Enrollment Lifecycle
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.
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
The high-level flow is:
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:
Diagram summary:
- Vitable fires
enrollment.startedwhen the enrollment window opens. The enrollment ispending. - The employee selects a plan. Vitable fires both
enrollment.electedandenrollment.acceptedtogether. The enrollment moves toenrolled. - Payroll deductions are generated later on a monthly schedule —
employee.deduction_createdfires 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:
- A qualifying life event triggers a new enrollment window. Vitable fires
enrollment.started. - The employee selects a plan. Vitable fires both
enrollment.electedandenrollment.acceptedtogether. The enrollment moves toenrolled. - 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:
- The enrollment’s status field reads
inactive, notterminated.
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.
Related Webhooks
Related API Endpoints
GET /v1/employees/{id}/enrollments— List all enrollments for an employeePOST /v1/members/{member_id}/dependents— Save a dependent for a memberGET /v1/enrollments/{id}— Retrieve a single enrollment by IDGET /v1/webhook-events— Query webhook events to recover missed deliveriesGET /v1/webhook-events/{event_id}/deliveries— Inspect delivery attempts for a specific event

