pass_series.leg_dropped
A cancelled date left a holder's season with no replacement, carrying the pro-rata figure.
When this fires
A creator cancelled a window that a season ticket points at, no replacement was available, and that date simply left one holder's season. The rest of their slate is untouched.
This is the event that carries money information, and it is the one worth wiring up: it is the only durable record of who is owed what.
refund_per_holder is a figure, not an action
Subscriby works out what the date was worth as a share of what the holder paid, and reports it. It does not move any money. Nothing is charged or refunded automatically, and nothing in the dashboard marks the subscription as owing.
A Zap that reads this as "already refunded" will double-refund. A Zap that reads it as "this is what you owe" is correct.
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_dropped",
"created_at": "2026-10-18T16:40:12Z",
"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": "9a4c1b58-2e37-4d80-b6f1-0c7e35a92b41",
"from_window_id": "3d5a8c72-b016-4e94-8fa7-61c209d4e738",
"from_starts_at": "2026-11-29T18:00:00Z",
"from_ends_at": "2026-11-29T21:00:00Z",
"passes_remaining": 3,
"refund_per_holder": "9.90",
"currency": "USD",
"amount_paid": "99.00",
"series_ended": false
}
}Field reference
| Field | Type | Notes |
|---|---|---|
id | string ULID | Unique event id, prefixed evt_. Use for idempotent processing. |
type | string | Always pass_series.leg_dropped 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 that lost a date. |
data.subscriber_id | string UUID | The holder. |
data.plan_id | string UUID | The plan the window belonged to — the source pass plan, not the series. |
data.from_window_id | string UUID | The cancelled window. |
data.from_starts_at | ISO 8601 timestamp | Cancelled window start, always UTC. |
data.from_ends_at | ISO 8601 timestamp | Cancelled window end, always UTC. |
data.passes_remaining | integer | How many dates this holder still has ahead of them. 0 means their season is over. |
data.refund_per_holder | string decimal | What this date was worth to this holder. A figure, not a refund — see above. |
data.currency | string | ISO code the amounts are in. |
data.amount_paid | string decimal | What they originally paid for the whole season. |
data.series_ended | boolean | true when this was the last date they had left. |
Caveats
- The figure is per holder, not per cancellation. Two people can be owed different amounts for the same cancelled date — a mid-season buyer paid the same price for fewer dates, so each of theirs is worth more. Sum the events; do not multiply one by a headcount.
series_ended: trueis worth handling separately. That holder's season is over, and an upsell to "the next date" is exactly wrong for someone who has just been told there is not one.- The same three groups skipped by substitution are skipped here too — paused, past due, and holders who cancelled their own series. They produce no event and are absent from any total you compute from these.
- Refunds are issued from your payment provider's dashboard. See Refunds.
Related events
pass.holder_stranded— the equivalent on an ordinary single-window pass, where the whole purchase ends rather than one date of it.pass_series.leg_substituted— what happens when a replacement exists.pass.window_cancelled— fires once, last, carrying both tallies.- Pass series events overview — back to family overview.
How is this guide?