pass_series.leg_completed
One date of a holder's season finished, with their running attendance.
When this fires
One window on a season ticket closed for one holder. It fires per holder per date, so a ten-date season sold to forty people emits four hundred of these across its run.
It is the progress event. Where pass.holder_missed
answers "did they turn up", this answers "where are they in the season" — which is what an
integration syncing a timetable or scoring engagement actually needs.
Required ability
project-subscription:view — token must carry this at mint time to subscribe an endpoint to this event.
Payload
{
"id": "evt_01HX...",
"type": "pass_series.leg_completed",
"created_at": "2026-10-25T21:00:06Z",
"api_version": "2026-05-01",
"project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",
"data": {
"subscription_id": "5b7e2d40-1a86-4c39-97f2-e83d0b16c5a4",
"subscriber_id": "2a91c4e7-6f38-4b52-8e0d-9c1a7b3f5d80",
"plan_id": "c4e82f16-93a7-4d5b-b81c-6e0f27a94d3b",
"window_id": "3d5a8c72-b016-4e94-8fa7-61c209d4e738",
"source_plan_id": "9a4c1b58-2e37-4d80-b6f1-0c7e35a92b41",
"granted": true,
"joined": true,
"passes_total": 10,
"passes_attended": 4,
"passes_missed": 1,
"passes_remaining": 5
}
}Field reference
| Field | Type | Notes |
|---|---|---|
id | string ULID | Unique event id, prefixed evt_. Use for idempotent processing. |
type | string | Always pass_series.leg_completed for this event. |
created_at | ISO 8601 timestamp | Server-side emission time. |
api_version | string | Webhook API contract version. |
project_id | string UUID | Project this event belongs to. |
data.subscription_id | string UUID | The season ticket. |
data.subscriber_id | string UUID | The holder. |
data.plan_id | string UUID | The series plan they bought. |
data.window_id | string UUID | The date that just finished. |
data.source_plan_id | string UUID | The pass plan that date belongs to — a series can mix several. |
data.granted | boolean | Whether access was actually issued to them for this date. |
data.joined | boolean | Whether they actually joined. Granted and joined are different questions — see below. |
data.passes_total | integer | Dates on this holder's slate. |
data.passes_attended | integer | How many they have joined so far, including this one. |
data.passes_missed | integer | How many they have missed so far. |
data.passes_remaining | integer | How many are still ahead of them. 0 means this was their last — see the caveat. |
Caveats
grantedandjoinedare tracked separately on purpose. Granted means Subscriby admitted them; joined means they actually appeared. A date can be granted and not joined — a held request approved into a channel the person never opened — and that distinction is the difference between a delivery problem and a disengaged customer.passes_attended + passes_missedwill not equalpasses_totalmid-season. The remainder is dates still ahead, plus any marked as already covered because the holder owned them individually before buying the series.- When
passes_remainingreaches0,pass_series.completedfires for the same holder in the same pass. Both are emitted; de-duplicate if you only want one. - Two holders of the same series reach the same date at different points in their own season if one bought mid-way — the counts are per purchase, never per plan.
Related events
pass_series.completed— the final date of a holder's season.pass.holder_missed— the same close, when they did not turn up.pass.window_closed— the window-level aggregate.- Pass series events overview — back to family overview.
How is this guide?