pass_series.purchased
Someone bought a season ticket and their whole slate was issued.
When this fires
A Pass Series was bought — or comped with an access code — and every date on the slate has been written into the buyer's ledger. They hold all of them from this moment; there is nothing further to claim.
This fires in addition to subscription.activated
A series is a subscription row, so a purchase emits both. An endpoint
subscribed to the two receives two deliveries for one purchase and must
de-duplicate on subscription_id.
That is not a quirk to work around — subscription.activated is what you want
for revenue, and this is what you want for the slate. They answer different
questions about the same event.
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.purchased",
"created_at": "2026-09-01T11:03:44Z",
"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",
"plan_name": "Autumn Season Ticket",
"passes_total": 10,
"passes_granted": 9,
"passes_already_held": 1,
"starts_at": "2026-09-20T13:00:00Z",
"ends_at": "2026-11-29T22:00:00Z",
"seat_cap": 50,
"seats_remaining": 13
}
}Field reference
| Field | Type | Notes |
|---|---|---|
id | string ULID | Unique event id, prefixed evt_. Use for idempotent processing. |
type | string | Always pass_series.purchased 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 purchase. De-duplicate on this against subscription.activated. |
data.subscriber_id | string UUID | The project user who bought. |
data.plan_id | string UUID | The series plan. |
data.plan_name | string | Its name at purchase time. |
data.passes_total | integer | How many dates this purchase covers. |
data.passes_granted | integer | Dates newly issued to them. |
data.passes_already_held | integer | Dates they already held individually. Not issued twice and not charged twice — see below. |
data.starts_at | ISO 8601 timestamp | The whole slate's first date start — not this buyer's first date. Always UTC. |
data.ends_at | ISO 8601 timestamp | The whole slate's last date end — not this buyer's last date. Always UTC. |
data.seat_cap | integer | null | The series seat limit, or null when uncapped. |
data.seats_remaining | integer | null | Seats left after this purchase, or null when uncapped. |
Caveats
passes_granted + passes_already_held = passes_total. Buying a date you already own is allowed on purpose; it is de-duplicated at grant time rather than refused, and the buyer is told so on their timeline. A non-zeropasses_already_heldis normal, not an error.passes_totalcounts what this buyer received, which on a mid-season purchase is fewer than the slate holds — dates that have already run are never issued. Read the plan if you need the full slate length.- The span is the series', not this buyer's.
starts_atandends_atare the plan's cached slate span, so on a mid-season purchasestarts_atcan precede every date the buyer actually holds. Only thepasses_*counts are buyer-scoped. - A series absorbing new dates later does not re-emit this. That is
pass_series.leg_added.
Related events
subscription.activated— the same purchase as revenue. De-duplicate onsubscription_id.pass_series.leg_completed— one date of this slate finishing.pass_series.completed— the final date finishing.pass_series.seats_exhausted— when a purchase takes the last seat.- Pass series events overview — back to family overview.
How is this guide?