Skip to navigation

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.

Quick Reference

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

There is no terminated enrollment status. When an enrollment is terminated, it transitions to inactive. The enrollment.terminated event signals the transition, but the resulting status on the enrollment resource is inactive.

Mapping Statuses

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)

Understanding the Events

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.

Starting an Enrollment

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.

Granting an Enrollment Opportunity

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.

Electing and Confirming Coverage

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.

After receiving enrollment.accepted, fetch the enrollment with GET /v1/enrollments/{id} to read the employee_deduction_in_cents and employer_contribution_in_cents fields. These values are set once coverage is confirmed.

Waiving Coverage

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.

Terminating Coverage

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.

Enrolling Through Open Enrollment

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

enrollment.started (status: pending) enrollment.elected + enrollment.accepted (status: enrolled) Employee views plans Deductions generated on monthly schedule Vitable API Partner

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.

Employees receive separate enrollments for each benefit category they are eligible for. A single employee may trigger enrollment.started for Medical, Dental, Vision, and Hospital independently.

Enrolling Through a Qualifying Life Event

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:

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.

Adding a Dependent

Saving a dependent and covering them are separate steps. Call POST /v1/members/{member_id}/dependents with the dependent’s name, date of birth, relationship (Spouse or Child), and address. Saving creates no coverage and does not change the member’s coverage tier.

The response has the same fields as a row from GET /v1/members/{member_id}/dependents, read from the saved record: the dependent’s own member_id, primary_member_id, name, relationship, date of birth, age, and sex at birth. It also includes legal_dependent_id and created. The endpoint returns 201 with created: true when it creates a new relationship, and 200 with created: false when the member already has this dependent, so saving the same dependent twice is safe.

Every save sets the dependent’s address to the one you send. When a new relationship is created at exactly the member’s street address, Vitable also adds the dependent to the member’s household where it can. This step is best effort and never fails the request; check the result with GET /v1/members/{member_id}/household.

If a later enrollment attempt fails or is abandoned, the saved dependent stays in place, so there is nothing to clean up. This endpoint does not accept a Social Security number.

Terminating an Enrollment

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.

When you receive enrollment.terminated, check the coverage_end field on the enrollment to determine the last day of active coverage. This is important for payroll integration — you may need to adjust deductions for partial coverage periods.

Troubleshooting

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