Skip to content
Embedded Benefits
Concepts

Enrollment Lifecycle

How enrollments move through statuses and which webhook events fire at each transition.

An enrollment represents an employee’s participation in a benefit plan. Each enrollment moves through a defined set of statuses as the employee selects, confirms, or declines coverage. Vitable fires webhook events at every transition, giving your system a real-time view of where each enrollment stands.

Enrollment statuses apply across all benefit categories: Medical, Dental, Vision, and Hospital.

This table maps every enrollment status transition to the webhook event that triggers it.

Current StatusEvent FiredNew StatusWhat Happened
(none)enrollment.startedpendingAn enrollment window opened for the employee
pendingenrollment.grantedpendingThe system or an admin created the enrollment opportunity — the employee still needs to make an election
pendingenrollment.elected + enrollment.acceptedenrolledThe employee selected a plan and confirmed — both events fire together from the same action
pendingenrollment.waivedwaivedThe employee declined the benefit
enrolledenrollment.terminatedinactiveCoverage has ended
StatusMeaningEventsTransitions To
pendingEnrollment window opened, employee has not actedenrollment.started, enrollment.grantedenrolled, waived
enrolledEmployee selected a plan and confirmedenrollment.elected + enrollment.accepted (both fire together)inactive
waivedEmployee declined the benefitenrollment.waived
inactiveCoverage has endedenrollment.terminated

The high-level flow is:

pending -> enrolled (elected + accepted fire together)
pending -> waived
enrolled -> inactive (via terminated)

Vitable fires six enrollment webhook events. Each event follows the standard webhook payload format with event_id, event_name, resource_type, resource_id, and created_at fields. After receiving any enrollment event, fetch the current state by calling GET /v1/enrollments/{id} with the resource_id from the payload.

When an employee becomes eligible for benefits and an enrollment window opens, Vitable fires enrollment.started. The enrollment is created with a status of pending. This event fires during both open enrollment periods and qualifying life event (QLE) windows.

At this point, the employee can view available plans and make a selection.

The enrollment.granted event fires when the system or an admin creates an enrollment opportunity for the employee. The enrollment remains in pending status — the employee still needs to make an election. This is distinct from enrollment.started, which signals the enrollment window opened.

When the employee selects a plan, signs, and confirms in the enrollment widget, Vitable fires both enrollment.elected and enrollment.accepted together. The enrollment moves from pending to enrolled in a single step — there is no intermediate state between selecting a plan and confirming coverage. Once enrolled, the employee has active coverage and the enrollment’s coverage_start field reflects when coverage began.

If the employee declines a benefit, Vitable fires enrollment.waived and the enrollment moves to the waived status. The answered_at field on the enrollment records when the employee made this decision.

When active coverage ends, Vitable fires enrollment.terminated and the enrollment transitions to inactive. The enrollment’s terminated_at and coverage_end fields reflect when coverage was terminated and when it officially ends.

Coverage can end for several reasons: the employee leaves the company, the employer terminates the plan, or an administrative change removes the employee’s eligibility. Regardless of the cause, the resulting status is always inactive.

During an open enrollment period, all eligible employees receive enrollment windows for each available benefit category. The typical flow looks like this:

sequenceDiagram
    participant V as Vitable API
    participant P as Partner
    V--)P: enrollment.started (status: pending)
    Note over P: Employee views plans
    V--)P: enrollment.elected + enrollment.accepted (status: enrolled)
    Note over V: Deductions generated on monthly schedule

Diagram summary:

  1. Vitable fires enrollment.started when the enrollment window opens. The enrollment is pending.
  2. The employee selects a plan. Vitable fires both enrollment.elected and enrollment.accepted together. The enrollment moves to enrolled.
  3. Payroll deductions are generated later on a monthly schedule — employee.deduction_created fires on the 1st of each month, not immediately after enrollment.

An employee may also choose to waive coverage, in which case step 2 would be replaced by an enrollment.waived event and the enrollment would move to waived.

A qualifying life event (QLE) — such as marriage, the birth of a child, or loss of other coverage — opens a special enrollment window outside the regular open enrollment period. The webhook event flow is the same:

sequenceDiagram
    participant V as Vitable API
    participant P as Partner
    Note over V,P: Qualifying life event occurs
    V--)P: enrollment.started (status: pending)
    V--)P: enrollment.elected + enrollment.accepted (status: enrolled)
    Note over V: Deductions generated on monthly schedule

Diagram summary:

  1. A qualifying life event triggers a new enrollment window. Vitable fires enrollment.started.
  2. The employee selects a plan. Vitable fires both enrollment.elected and enrollment.accepted together. The enrollment moves to enrolled.
  3. Payroll deductions are generated later on a monthly schedule — not immediately after enrollment.

The QLE flow produces the same sequence of events as open enrollment. The difference is the trigger — a qualifying life event rather than a scheduled enrollment period.

When an enrolled employee’s coverage ends, the enrollment transitions from enrolled to inactive:

FromToEventNotes
enrolledinactiveenrollment.terminatedThere is no terminated status — the enrollment moves to inactive
  • The enrollment’s status field reads inactive, not terminated.

Receiving events out of order. Webhook events are delivered at least once and may arrive out of order. Always fetch the current enrollment state with GET /v1/enrollments/{id} after receiving an event rather than relying on event order to track status. If you receive enrollment.accepted before enrollment.elected, the enrollment’s status field from the API reflects the true current state.

Duplicate events. Your endpoint may receive the same event more than once. Use the event_id field for idempotency — if you have already processed an event, skip it.

Missing events. If your endpoint was unreachable, use GET /v1/webhook-events to query for events you may have missed. You can filter by resource_type and time range to find enrollment events. For delivery details on a specific event, call GET /v1/webhook-events/{event_id}/deliveries.

Enrollment stuck in pending. An enrollment remains pending until the employee makes a selection and it is processed, or the enrollment window closes. If an enrollment has been pending longer than expected, the employee may not have completed their enrollment yet.

EventDescription
enrollment.startedAn enrollment window has opened for an employee. Learn more
enrollment.electedAn employee has selected a benefit plan. Learn more
enrollment.acceptedAn enrollment has been confirmed and coverage is active. Learn more
enrollment.grantedAn enrollment has been granted through auto-enrollment or admin action. Learn more
enrollment.waivedAn employee has declined a benefit. Learn more
enrollment.terminatedCoverage has ended and the enrollment is now inactive. Learn more
employee.deduction_createdPayroll deductions generated on monthly schedule for employees with active enrollments. Learn more