Skip to main content

Enrollment funnel events

Intro

Enrollment funnel events record steps in the enrollment journey that Light's other APIs do not log. Examples include when plans are shown to a customer or when they decline to continue. Light uses these events to measure funnel conversion and help you reduce customer drop-off.

Use these endpoints for steps Light's APIs do not already capture. Server-side API actions are already recorded by Light. For example, calling POST /v1/app/accounts/enroll/plans/request automatically logs a plan request. You do not need a separate event for that API call.

You can call these endpoints from your backend using your app token (authentication). Do not expose your app token in a browser. No account token is required because these events usually happen before an account is created in Light.

API vs embedded flow

Both custom API integrations and prebuilt UI flows can send funnel events.

Prerequisites

  • An app token for your sandbox or production app
  • A custom enrollment flow where you control when plans are presented and when opt-out decisions are recorded (in a UI, server-rendered page, concierge tool, etc.)

Endpoints

All endpoints require your app token and return 401 without valid authentication.

Supported events

POST /v1/app/accounts/events records a funnel event. Pass one of these values in the event field:

event valueWhen to call
plans_viewedPlans are presented to the customer, not when you fetch or cache plan data
enrollment_opt_outThe customer declines to continue enrollment (for example confirms an opt-out modal or tells a rep they are not switching)

See Record plans_viewed and Showing an opt-out modal for request examples.

Routes

EndpointPurpose
GET /v1/app/accounts/enroll/opt-out/reasonsFetch suggested opt-out survey options (used before showing an opt-out confirmation modal)
POST /v1/app/accounts/eventsRecord a supported event (see table above)

Identity tracking

Each event is attributed to a customer using one of these identity strategies:

StrategyWhen to useFields
ProspectYou know the customer's email before they have a Light accountprospect.email
AccountYou have already created a Light accountaccount_uuid, or prospect.email that matches an existing account
AnonymousYou do not yet have identifying data for this customerOmit prospect and account_uuid

When you later create an account with POST /v1/app/accounts, pre-account events recorded with the same prospect.email are merged into the new account in analytics.

Record plans_viewed

Call this when plan cards or a plan comparison is presented to the customer, typically right after you render the response from POST /v1/app/accounts/enroll/plans/request. This can happen from your frontend after render, or from your backend when serving a server-rendered page.

Fire plans_viewed when the customer can see a screen like the one above, not when plans are fetched or cached in the background.

Identified prospect

curl -X POST "https://api.light.dev/v1/app/accounts/events" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event": "plans_viewed",
"prospect": { "email": "jane.smith@example.com" },
"plan_uuids": ["a14b20c3-809a-483f-68dc-3d35911eec04", "b25c31d4-910b-594e-79ed-4e46022ffdd5"]
}'

Anonymous visitor

Use when you do not yet have identifying data for the customer:

curl -X POST "https://api.light.dev/v1/app/accounts/events" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event": "plans_viewed",
"plan_uuids": ["a14b20c3-809a-483f-68dc-3d35911eec04"]
}'

Existing account

Request
{
"event": "plans_viewed",
"account_uuid": "c36d42e5-021c-6a5f-8afe-5f57133ggee5",
"plan_uuids": ["a14b20c3-809a-483f-68dc-3d35911eec04"]
}
Do not log exposure on plan request

Calling plans/request does not record a plans_viewed event. Fire plans_viewed only when the customer actually sees the plans, not when plans are fetched or cached in the background.

Showing an opt-out modal

In many enrollment flows, customers see a plan or set of plan options with a way to skip the step or opt out of switching. That primary action should open a follow-up modal rather than silently dropping the customer.

Use a confirmation modal to understand why they are not interested. Light provides suggested reason codes and labels via GET /v1/app/accounts/enroll/opt-out/reasons.

  1. Show plans: Fire plans_viewed when the customer can see the options.
  2. Customer opts out: Open your confirmation modal; do not fire enrollment_opt_out yet.
  3. Fetch reasons: Load survey options from GET /accounts/enroll/opt-out/reasons.
  4. Customer confirms: Fire enrollment_opt_out with the selected reason and label when needed.
  5. Dismiss without a reason: You may still fire enrollment_opt_out without reason or label.
Fire on confirm, not on open

Record enrollment_opt_out when the customer confirms they are opting out, not when they first tap skip on the plan screen.

Record the opt-out

If the customer viewed plans before opting out, include the same plan_uuids:

curl -X POST "https://api.light.dev/v1/app/accounts/events" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event": "enrollment_opt_out",
"prospect": { "email": "jane.smith@example.com" },
"plan_uuids": ["a14b20c3-809a-483f-68dc-3d35911eec04"],
"reason": "no_real_savings",
"label": "I don'\''t see real savings"
}'

reason and label are optional.

Request fields

See the POST /v1/app/accounts/events API reference for the full request schema.

The request body is a discriminated union on event: shared fields apply to both event types, and reason / label are only valid for enrollment_opt_out. Event-specific fields are rejected on the wrong event type (for example reason is not valid on plans_viewed).

For opt-out reason codes, fetch options from GET /v1/app/accounts/enroll/opt-out/reasons, use other, or send a partner-defined code with a matching label.

Example: fire plans_viewed after render (frontend)
async function onPlansRendered(plans, { email }) {
const body = {
event: "plans_viewed",
plan_uuids: plans.map((plan) => plan.uuid),
};

if (email) {
body.prospect = { email };
}

await fetch("https://api.light.dev/v1/app/accounts/events", {
method: "POST",
headers: {
Authorization: `Bearer ${appToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
}

Response

All successful event submissions return 201:

Response
{
"recorded": true
}

Events are processed asynchronously. A 201 response means the event was accepted for recording, not that it has already appeared in analytics dashboards.

What Light records automatically

You do not need funnel events for these server-side actions:

ActionRecorded by
Plan requestPOST /v1/app/accounts/enroll/plans/request
Plan acceptancePOST /v1/account/enroll/plans/accept
Account creationPOST /v1/app/accounts (including prospect identity merge)
Enrollment finalizationPOST /v1/account/enroll/finalize

Use funnel events for steps Light's APIs do not record on their own: seeing plans and choosing not to enroll.