> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bindbee.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Detect COBRA Qualifying Events

> Infer qualifying life events from HRIS changes so continuation coverage notices go out inside the statutory window.

The HRIS doesn't announce a qualifying event. It records a status change, a date, or a dependent's details - and you infer the event from those.

## What you'll use

**Models** - employee, employment, dependent, dependent benefit, benefit
**Events** - `employee_data_changed`, `connector_data_modified`, `connector_synced`

<Info>
  **Before you start**

  * You know which events the customer's plan treats as qualifying, and the notice deadline that applies.
  * You can run a scheduled job as well as handle events. Two of the four detections below cannot be event-driven.
</Info>

## Steps

<Steps>
  <Step title="Subscribe to both change events">
    `employee_data_changed` covers status and employment; `connector_data_modified` covers the other models, including dependents - see [Choose Webhook Events](/guides/reading-writing/webhooks).

    Subscribing to the employee event alone misses dependent changes entirely, which is where two of the four qualifying events show up.

    **Result:** IDs for both employee and related-model changes.
  </Step>

  <Step title="Detect loss of coverage from termination">
    Fetch the changed employees and match the terminal statuses.

    ```bash theme={null}
    curl --request GET \
      --url 'https://api.bindbee.dev/api/hris/v1/employees?ids=<ID_1>,<ID_2>' \
      --header 'Authorization: Bearer <BINDBEE_API_KEY>' \
      --header 'X-Connector-Token: <CONNECTOR_TOKEN>'
    ```

    `INACTIVE`, `RETIRED`, `DECEASED` and `INACTIVE_EXTERNAL` are all departures - see [Identify a Termination](/get-started/use-cases/identify-a-termination) for handling them properly.

    **Result:** Candidate loss-of-coverage events.
  </Step>

  <Step title="Establish the actual coverage end date">
    The qualifying date is when coverage ends, not when employment does.

    ```bash theme={null}
    curl --request GET \
      --url 'https://api.bindbee.dev/api/hris/v1/benefits?employee_id=<EMPLOYEE_ID>&page_size=200' \
      --header 'Authorization: Bearer <BINDBEE_API_KEY>' \
      --header 'X-Connector-Token: <CONNECTOR_TOKEN>'
    ```

    Read the benefit's end date. Statutory clocks run from this, so taking the termination date instead can start the count in the wrong month.

    **Result:** The date the notice window opens.
  </Step>

  <Step title="Find who else loses coverage">
    Covered dependents are affected people in their own right. Read coverage, not dependent records.

    ```bash theme={null}
    curl --request GET \
      --url 'https://api.bindbee.dev/api/hris/v1/dependent-benefits?page_size=200' \
      --header 'Authorization: Bearer <BINDBEE_API_KEY>' \
      --header 'X-Connector-Token: <CONNECTOR_TOKEN>'
    ```

    A dependent record means the employee recorded that person; a dependent benefit means they were actually enrolled - see [Reading benefits](/guides/data-models/benefits).

    **Result:** Every qualified beneficiary, not everyone on file.
  </Step>

  <Step title="Run a scheduled job for aging-out">
    No event fires when a dependent reaches the limiting age, because nothing changed upstream. This detection has to be forward-looking.

    ```python theme={null}
    # daily
    for dep in all_covered_dependents(connector):
        turns_limit_on = dep["date_of_birth"].replace(year=dep["date_of_birth"].year + LIMIT_AGE)
        if today <= turns_limit_on <= today + timedelta(days=NOTICE_LEAD_DAYS):
            queue_notice(dep, qualifying_date=turns_limit_on)
    ```

    Waiting for a notification here means never detecting it.

    **Result:** Upcoming thresholds, ahead of the deadline rather than after it.
  </Step>

  <Step title="Watch for the events you can only infer">
    Divorce and legal separation are usually not recorded as such. What you can observe is a dependent's coverage disappearing, or a coverage tier changing, with no field stating why.

    Treat those as candidates for review rather than as confirmed events - flag them for a human, and accept that some qualifying events must reach you through the customer instead.

    **Result:** A queue of probable events, honestly labeled.
  </Step>
</Steps>

## Constraints specific to this combination

**Only some qualifying events are visible in an HRIS.** Termination and reduction of hours are detectable from employment status and hours. A dependent aging out is derivable from date of birth. Death is sometimes carried as a status. Divorce and legal separation usually are not - they surface, if at all, as a dependent silently disappearing or a coverage tier changing, with no field stating the reason. Build detection for what's observable and accept that some events must reach you another way.

**Detection latency is the sync cadence, and the clock is statutory.** Continuation notice deadlines run from the event date, not from when you learned about it. A 24-hour cadence plus your own processing interval can consume several days of a fixed window before you begin. For customers where this matters, shorten the cadence - see [Sync schedules](/guides/reading-writing/syncing) - and treat a stalled connector as a compliance risk rather than a data-freshness issue.

**A dependent record is not coverage.** Whether a dependent loses coverage depends on their dependent benefit records, not on their existence as a dependent. Reading dependents alone overstates who is affected.

**Aging-out needs a forward-looking job, not an event.** No change event fires when a dependent reaches the limiting age - nothing changed upstream. Run a scheduled query against dependent dates of birth to find upcoming thresholds, rather than waiting for a notification that will never arrive.

**Termination date and benefit end date are independent.** Coverage does not end when employment does; the source system usually ends it as a separate action, sometimes weeks later. Use the benefit's own end date to establish the loss-of-coverage date, and expect a period where an employee is terminated with coverage still active.

## Frequently Asked Questions

<AccordionGroup>
  <Accordion title="Dependent changes never arrive">
    Only `employee_data_changed` is subscribed. Dependent records change under `connector_data_modified`.
  </Accordion>

  <Accordion title="date_of_birth is null for every dependent">
    Almost always a permission, not absence - sensitive fields are commonly gated behind a separate grant. Without it, aging-out cannot be detected at all. See [Origin-system errors](/guides/troubleshooting/errors#origin-system-errors).
  </Accordion>

  <Accordion title="The notice window was already half gone">
    Sync cadence plus processing interval. The clock runs from the event date, not from detection - shorten the cadence for customers where the margin matters.
  </Accordion>

  <Accordion title="Notices went to people who weren't covered">
    Dependents were counted instead of dependent benefits.
  </Accordion>
</AccordionGroup>

## Related

* [Reading benefits](/guides/data-models/benefits)
* [Choose Webhook Events](/guides/reading-writing/webhooks)
* [Identify a Termination](/get-started/use-cases/identify-a-termination)
