Skip to main content

Sync enrollments to your LMS

Goal: a participant has access to the LMS course exactly while their Eduframe enrollment says they should, and loses it when the enrollment is canceled.

The enrollment lifecycle​

An enrollment links a student (a user) to a planned course. Its status drives access:

statusMeaningLMS action
confirmedSeat is confirmed, edition has not started.Enroll the participant. Many LMSs let you enroll now with a future start date.
activeEdition has started for this participant.Participant must be enrolled and active.
completedEdition or access window has ended.Keep enrolled for read access, or set the enrollment end date; do not remove.
canceledEnrollment was canceled.Unenroll or suspend.

Fields to map​

From GET /api/v1/enrollments/{id}:

FieldUse it for
idExternal identifier on the LMS enrollment, if the LMS has one.
student_idThe user to enroll. Create the LMS user on demand, see Sync users.
planned_course_idThe LMS course to enroll into, see Sync courses.
statusDrives the LMS action as in the table above.
start_date, end_dateAccess window. Equal to the edition dates for fixed planned courses; per participant for flexible planned courses.
graduation_stateOptional: mark completion in the LMS.

Trimmed example:

{
"id": 4711,
"student_id": 9021,
"planned_course_id": 4211,
"order_id": 3300,
"status": "confirmed",
"graduation_state": "awaiting_judgement",
"start_date": "2026-10-05",
"end_date": "2026-10-07",
"updated_at": "2026-09-14T08:12:44.310Z"
}

Webhook events to subscribe to​

EventWhat to do
enrollment.createdFetch the enrollment. If status is confirmed or active, ensure user and course exist in the LMS and enroll.
enrollment.updatedFetch and apply the status table. This is also how you notice a cancellation or a changed access window.
enrollment.deletedUnenroll or suspend in the LMS.

Always fetch the enrollment after enrollment.created and enrollment.updated; do not derive status from the event name. An enrollment can be created directly in status confirmed, and a status change from confirmed to canceled arrives as enrollment.updated. After enrollment.deleted the record can no longer be fetched, so resolve the LMS enrollment from the mapping you stored when you first saw it.

Special cases​

Flexible courses​

On a FlexiblePlannedCourse every enrollment has its own start_date and end_date. Pass those to the LMS as the participant's access window instead of using the edition dates. How this works, and what to do when your LMS has no per-enrollment dates, is covered in Flexible courses and access windows.

Publishing courses with enrollments​

A course template can be marked for the LMS after its editions already have enrollments, for example when you roll out the integration to an existing catalog or when an administrator adds the marker later. No enrollment.* events fire for those existing enrollments, so the middleware has to backfill.

When a course template comes into scope, whether you notice that through course.updated or through a change in your own configuration:

  1. List its editions with GET /api/v1/courses/{course_id}/planned_courses and create an LMS course for every planned course with status planned or active.
  2. For each of those, page through GET /api/v1/enrollments?planned_course_id={id}&per_page=100 and enroll every participant whose status is confirmed, active or completed, applying the LMS action from the lifecycle table so completed participants keep their read access.
  3. Set the LMS course link on each planned course, see Show the LMS course link.

The same routine serves as the initial import for the whole integration and as a repair job when you suspect the LMS has drifted from Eduframe. Because every step is an upsert keyed on the Eduframe id, running it twice is harmless.

Multiple course enrollments​

Eduframe does not enforce one enrollment per participant per edition. A participant can hold several enrollments on the same planned course, each with its own status, for example a canceled enrollment from a first order and a confirmed one from a second order. Most LMSs allow only one enrollment per user per course, so you cannot map enrollments one to one.

Derive the LMS enrollment from the set of Eduframe enrollments for that participant and edition:

  1. On every enrollment.* event, fetch the enrollment to learn its student_id and planned_course_id. Store that pair together with the enrollment id the first time you see it: after enrollment.deleted the record can no longer be fetched, so the pair has to come from your own store.
  2. Fetch all enrollments for that pair with GET /api/v1/enrollments?student_id={student_id}&planned_course_id={planned_course_id}&with_canceled=true.
  3. The participant should be enrolled in the LMS if at least one enrollment is confirmed, active or completed. Only when every enrollment is canceled do you unenroll or suspend.
  4. Use the widest access window across the non-canceled enrollments.

Key your mapping on the pair of student_id and planned_course_id rather than on the enrollment id. When you send grades back, attach them to the most recent non-canceled enrollment, see Send grades.