pass_series.* events
Season-ticket lifecycle: bought, each date completing, dates added or dropped mid-season, and the holders-only presale on the next one.
Season-ticket lifecycle: bought, each date completing, dates added or dropped mid-season, and the holders-only presale on the next one. Fires across the life of a Pass Series, a season ticket sold once that grants a whole slate of other pass plans' dated windows.
The family exists because a series is not just a pass with more dates. A season grows when rules absorb new windows, changes when a date is cancelled, and progresses as each date runs, and none of that has an equivalent on a single-window pass. The pass.* family still fires for every underlying window, because each date is a real window on a real plan; these describe the slate.
Two payload facts integrators get wrong.
refund_per_holderonpass_series.leg_droppedis a figure, not an action. Subscriby never moves the money; the creator does. An automation that treats it as "already refunded" will double-refund.pass_series.purchasedfires in addition tosubscription.activated, because a series is a subscription row. An endpoint subscribed to both gets two deliveries for one purchase and must de-duplicate onsubscription_id.
Background
Which scope each event is about
| Scope | Events | Ability |
|---|---|---|
| The slate: the plan and its dates | leg_added, seats_exhausted, presale_opened | project-subscription-plan:view |
| One holder: a purchase and a person | purchased, leg_completed, leg_substituted, leg_dropped, completed | project-subscription:view |
The split mirrors the pass.* family, where window_* events describe the window and holder_* events describe a person. Subscribe to the scope you actually act on.
Building a season timetable elsewhere
The four events that keep an external calendar honest, in the order you need them:
pass_series.purchased: create the entries. Read the plan'spass_series.windowsfor the full slate.pass_series.leg_added: add an entry. Seasons composed with rules grow after they sell.pass_series.leg_substituted: move an entry, usingfrom_window_idandto_window_id.pass_series.leg_dropped: remove an entry.
Subscribers get this for free: the portal offers a calendar feed that stays in step on its own. The events are for putting the same schedule somewhere Subscriby cannot reach.
Example envelope
{
"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
}
}Events
pass_series.purchasedSomeone bought a season ticket and their whole slate was issued.pass_series.leg_addedAn automatic rule absorbed a newly-scheduled window into a live series.pass_series.leg_completedOne date of a holder's season finished, with their running attendance.pass_series.leg_substitutedA cancelled date in a season was replaced, and one holder was moved to the replacement.pass_series.leg_droppedA cancelled date left a holder's season with no replacement, carrying the pro-rata figure.pass_series.seats_exhaustedA capped season ticket sold its last seat.pass_series.completedA holder reached the last date of their season, with the final attendance tallies.pass_series.presale_openedA finished season's holders were given first refusal on the next one.pass_series.purchased
WEBHOOKSomeone 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 onsubscription_id. That is not a quirk to work around:subscription.activatedis what you want for revenue, and this is what you want for the slate. They answer different questions about the same event.
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.
Ability to subscribe
A token needs this to subscribe an endpoint to the event.
Header Parameters
t=<unix seconds>,v1=<hex>: the HMAC-SHA256 of "<t>.<raw body>" under the endpoint's secret. Verify it before acting, and refuse a t more than 300 seconds from now. During a secret rotation a v0= signature under the previous secret may precede v1=.
The event's ULID, bare. The envelope's id is the same ULID prefixed evt_, so strip the prefix before comparing. Deduplicate on it: a retry carries the same id.
The event name, the same as the envelope's type.
Always application/json.
Always Subscriby-Webhooks/1.0.
Payload
JSONWhat Subscriby posts to your endpointapplication/json
The signed JSON envelope posted to your endpoint.
The envelope every event is delivered in.
Responses
2XXAny success status
Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.
defaultAny other status
Any other status, a connection failure, or no answer within 30 seconds counts as a failed attempt. The delivery is retried 8 times, after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 1 day, 3 days; the last failure dead-letters it, and it can be retried from the dashboard or POST /v1/webhook-deliveries/{delivery}/retry. After 20 consecutive failures the endpoint is paused until it is resumed.
pass_series.leg_added
WEBHOOKAn automatic rule absorbed a newly-scheduled window into a live series.
When this fires
A Pass Series with Automatic Passes Selection matched a newly-scheduled window on one of its source plans, added it to the slate, and granted it to everyone currently holding the series, at no extra charge.
This is the event that makes an external calendar or CRM worth wiring up. A season composed with rules is not fixed at the point it sold: it grows, and holders are given what it grows by. Without this you would have to re-read the plan to notice.
Holders are messaged too. Every current holder receives a message on the connector naming the new date, and it appears on their timetable and in their calendar feed. Nothing is asked of them; their invite link arrives before it opens, as usual.
Caveats
- This fires once per window. One absorption run can take in several at a time, which arrives as several events rather than one carrying a list.
- Absorption respects Prevent Overlapping Passes. With the toggle on, a window that clashes with something already on the slate is refused rather than absorbed (the series will not hand holders two things at the same time) so a matching window can exist and never produce this event.
- A rule of type
next_nstops once it has taken its count and never tops itself back up. A window becoming free later does not make it reach for another. - A series is capped at 120 dates. A rule that would take the slate past the cap stops there.
- Holders who cancelled are not granted the new date.
- Manually handpicked dates never produce this event. Handpicking is a fixed list by design; only rules keep working after the save.
- A date the creator has excluded stays excluded. Unticking a rule-added pass records a permanent exclusion, and the rule will not put it back.
Related events
pass.window_scheduled: the window coming into existence, which is what a rule reacts to.pass_series.leg_substituted: a date being swapped rather than added.pass_series.leg_dropped: a date leaving a season.
Ability to subscribe
A token needs this to subscribe an endpoint to the event.
Header Parameters
t=<unix seconds>,v1=<hex>: the HMAC-SHA256 of "<t>.<raw body>" under the endpoint's secret. Verify it before acting, and refuse a t more than 300 seconds from now. During a secret rotation a v0= signature under the previous secret may precede v1=.
The event's ULID, bare. The envelope's id is the same ULID prefixed evt_, so strip the prefix before comparing. Deduplicate on it: a retry carries the same id.
The event name, the same as the envelope's type.
Always application/json.
Always Subscriby-Webhooks/1.0.
Payload
JSONWhat Subscriby posts to your endpointapplication/json
The signed JSON envelope posted to your endpoint.
The envelope every event is delivered in.
Responses
2XXAny success status
Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.
defaultAny other status
Any other status, a connection failure, or no answer within 30 seconds counts as a failed attempt. The delivery is retried 8 times, after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 1 day, 3 days; the last failure dead-letters it, and it can be retried from the dashboard or POST /v1/webhook-deliveries/{delivery}/retry. After 20 consecutive failures the endpoint is paused until it is resumed.
pass_series.leg_completed
WEBHOOKOne 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.
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.
Ability to subscribe
A token needs this to subscribe an endpoint to the event.
Header Parameters
t=<unix seconds>,v1=<hex>: the HMAC-SHA256 of "<t>.<raw body>" under the endpoint's secret. Verify it before acting, and refuse a t more than 300 seconds from now. During a secret rotation a v0= signature under the previous secret may precede v1=.
The event's ULID, bare. The envelope's id is the same ULID prefixed evt_, so strip the prefix before comparing. Deduplicate on it: a retry carries the same id.
The event name, the same as the envelope's type.
Always application/json.
Always Subscriby-Webhooks/1.0.
Payload
JSONWhat Subscriby posts to your endpointapplication/json
The signed JSON envelope posted to your endpoint.
The envelope every event is delivered in.
Responses
2XXAny success status
Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.
defaultAny other status
Any other status, a connection failure, or no answer within 30 seconds counts as a failed attempt. The delivery is retried 8 times, after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 1 day, 3 days; the last failure dead-letters it, and it can be retried from the dashboard or POST /v1/webhook-deliveries/{delivery}/retry. After 20 consecutive failures the endpoint is paused until it is resumed.
pass_series.leg_substituted
WEBHOOKA cancelled date in a season was replaced, and one holder was moved to the replacement.
When this fires
A creator cancelled a window that a season ticket points at, a replacement was available, and one holder was moved onto it. Their season carries on at the same length.
Fires once per holder, not once per cancellation. Each holder is resettled individually because the outcomes differ: someone already holding the replacement date skips past it to the next one they do not have, and someone with no replacement available is dropped instead.
Caveats
was_queuedis the field worth branching on. A holder who was queued stays queued for the new date and is told there is nothing to do. One who was not usually gets their invite link attached, except a holder who had already been admitted to the cancelled window: they also reportwas_queued: false, but their access is revoked and they are told a fresh link arrives when the new window opens, so no link is attached.was_queued: falsetherefore covers two different situations; if you need to tell them apart, check whether the holder currently has access. If you send your own message alongside ours, match that split or you will ask half your audience to do something they have already done.- The holder is never moved onto a date they already hold. The next one they do not have is chosen instead, so
to_window_idcan be further out than the immediate next window. - Three groups are skipped entirely and produce no event: holders who are paused, past due, or who cancelled their own series. They still paid for a date that is not happening. Check subscriptions on that plan before reaching for a refund list.
plan_idis the source plan. To identify the series, readsubscription_id.
Related events
pass.holder_moved: the same resettlement on an ordinary single-window pass rather than a series.pass_series.leg_dropped: what happens when no replacement exists.pass.window_cancelled: fires once, last, carrying both tallies.
Ability to subscribe
A token needs this to subscribe an endpoint to the event.
Header Parameters
t=<unix seconds>,v1=<hex>: the HMAC-SHA256 of "<t>.<raw body>" under the endpoint's secret. Verify it before acting, and refuse a t more than 300 seconds from now. During a secret rotation a v0= signature under the previous secret may precede v1=.
The event's ULID, bare. The envelope's id is the same ULID prefixed evt_, so strip the prefix before comparing. Deduplicate on it: a retry carries the same id.
The event name, the same as the envelope's type.
Always application/json.
Always Subscriby-Webhooks/1.0.
Payload
JSONWhat Subscriby posts to your endpointapplication/json
The signed JSON envelope posted to your endpoint.
The envelope every event is delivered in.
Responses
2XXAny success status
Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.
defaultAny other status
Any other status, a connection failure, or no answer within 30 seconds counts as a failed attempt. The delivery is retried 8 times, after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 1 day, 3 days; the last failure dead-letters it, and it can be retried from the dashboard or POST /v1/webhook-deliveries/{delivery}/retry. After 20 consecutive failures the endpoint is paused until it is resumed.
pass_series.leg_dropped
WEBHOOKA 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_holderis 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.
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.
Ability to subscribe
A token needs this to subscribe an endpoint to the event.
Header Parameters
t=<unix seconds>,v1=<hex>: the HMAC-SHA256 of "<t>.<raw body>" under the endpoint's secret. Verify it before acting, and refuse a t more than 300 seconds from now. During a secret rotation a v0= signature under the previous secret may precede v1=.
The event's ULID, bare. The envelope's id is the same ULID prefixed evt_, so strip the prefix before comparing. Deduplicate on it: a retry carries the same id.
The event name, the same as the envelope's type.
Always application/json.
Always Subscriby-Webhooks/1.0.
Payload
JSONWhat Subscriby posts to your endpointapplication/json
The signed JSON envelope posted to your endpoint.
The envelope every event is delivered in.
Responses
2XXAny success status
Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.
defaultAny other status
Any other status, a connection failure, or no answer within 30 seconds counts as a failed attempt. The delivery is retried 8 times, after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 1 day, 3 days; the last failure dead-letters it, and it can be retried from the dashboard or POST /v1/webhook-deliveries/{delivery}/retry. After 20 consecutive failures the endpoint is paused until it is resumed.
pass_series.seats_exhausted
WEBHOOKA capped season ticket sold its last seat.
When this fires
A purchase took the last seat on a series with a Seat Limit. The season is now sold out: its portal card reads Sold out, but a checkout already in flight is not blocked; the seat cap is only claimed once the payment lands, so a late buyer can pay, be given nothing, and be told to ask the organiser for a refund. Treat this event as the cue to stop promoting the season yourself.
Fires at the transition, once. An endpoint told this on every subsequent read would be told a fact rather than an event.
Caveats
- Sold out is not permanent. A seat is released when a holder cancels or is refunded, and the series goes back on sale, at which point a later purchase can exhaust it again and emit this a second time. Treat it as a state change, not a terminal state.
- Only holders who actually hold the series consume a seat. A checkout that was started and abandoned, or a payment that never cleared, does not.
- A series with no Seat Limit never emits this at all.
- Raising the cap does not emit anything. If you are tracking availability, read
seats_remainingon the plan rather than inferring it from this event alone.
Related events
pass_series.purchased: every purchase, each carryingseats_remaining.plan.deactivated: the series coming off sale for good once its dates have run.
Ability to subscribe
A token needs this to subscribe an endpoint to the event.
Header Parameters
t=<unix seconds>,v1=<hex>: the HMAC-SHA256 of "<t>.<raw body>" under the endpoint's secret. Verify it before acting, and refuse a t more than 300 seconds from now. During a secret rotation a v0= signature under the previous secret may precede v1=.
The event's ULID, bare. The envelope's id is the same ULID prefixed evt_, so strip the prefix before comparing. Deduplicate on it: a retry carries the same id.
The event name, the same as the envelope's type.
Always application/json.
Always Subscriby-Webhooks/1.0.
Payload
JSONWhat Subscriby posts to your endpointapplication/json
The signed JSON envelope posted to your endpoint.
The envelope every event is delivered in.
Responses
2XXAny success status
Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.
defaultAny other status
Any other status, a connection failure, or no answer within 30 seconds counts as a failed attempt. The delivery is retried 8 times, after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 1 day, 3 days; the last failure dead-letters it, and it can be retried from the dashboard or POST /v1/webhook-deliveries/{delivery}/retry. After 20 consecutive failures the endpoint is paused until it is resumed.
pass_series.completed
WEBHOOKA holder reached the last date of their season, with the final attendance tallies.
When this fires
The last date on one holder's season ticket closed. Their season is over.
Fires per holder, not per series, because "finished" is a fact about a purchase: two people holding the same season reach its end on different dates when one of them bought in late.
This is the renewal signal. The tallies are the half worth acting on. A completion with two of ten attended is a churn signal; the same completion with ten of ten is a renewal one. If the creator linked a next season, the holders-only presale opens around the same moment; see
pass_series.presale_opened.
Caveats
- The subscription is expired at this point, which is normal and not a failure. A dated purchase ends when its dates run out.
passes_totalis this holder's slate, not the plan's. A mid-season buyer received fewer dates for the same price and their totals reflect that.passes_attended + passes_missedcan be less thanpasses_totalwhen some dates were already covered by passes the holder owned individually; those were not issued twice.successor_plan_idbeing present does not mean the holder has been invited yet. The presale opens when the series retires, which is a separate event.- The plan itself is deactivated automatically once its last date has run for everybody. That arrives as
plan.deactivatedwithreason: series_exhausted.
Related events
pass_series.leg_completed: each date along the way, including this one.pass_series.presale_opened: the invitation to the next season.plan.deactivated: the series taking itself off sale.
Ability to subscribe
A token needs this to subscribe an endpoint to the event.
Header Parameters
t=<unix seconds>,v1=<hex>: the HMAC-SHA256 of "<t>.<raw body>" under the endpoint's secret. Verify it before acting, and refuse a t more than 300 seconds from now. During a secret rotation a v0= signature under the previous secret may precede v1=.
The event's ULID, bare. The envelope's id is the same ULID prefixed evt_, so strip the prefix before comparing. Deduplicate on it: a retry carries the same id.
The event name, the same as the envelope's type.
Always application/json.
Always Subscriby-Webhooks/1.0.
Payload
JSONWhat Subscriby posts to your endpointapplication/json
The signed JSON envelope posted to your endpoint.
The envelope every event is delivered in.
Responses
2XXAny success status
Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.
defaultAny other status
Any other status, a connection failure, or no answer within 30 seconds counts as a failed attempt. The delivery is retried 8 times, after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 1 day, 3 days; the last failure dead-letters it, and it can be retried from the dashboard or POST /v1/webhook-deliveries/{delivery}/retry. After 20 consecutive failures the endpoint is paused until it is resumed.
pass_series.presale_opened
WEBHOOKA finished season's holders were given first refusal on the next one.
When this fires
A season ticket ran its last date, the creator had linked a Next Series, and the holders-only window on that successor opened. Every eligible holder has been messaged with a link straight to it.
For the length of the window, nobody who did not hold the finished season can buy the successor; the portal and the bot both turn them away and say when it goes on general sale.
Who is invited is worked out now, not at checkout. Nothing is stamped onto a purchase when someone pays. Eligibility is resolved at the moment the presale opens, which is what lets a creator link a successor months after the season sold out and still reach everyone who bought it.
Caveats
- Fires once per season, ever. The window's opening is stamped, so a repeated sweep does not re-invite anybody. If you need to reach these people again, do it from your own side.
eligible_holderscounts everyone who bought the finished season and did not cancel it, whether or not their subscription has expired; by this point most have, which is the normal case and not a disqualification. Someone who cancelled partway through is excluded: they said they did not want the season.- It counts holders resolved for invitation, not messages the platform accepted. A holder who has blocked the bot is included here and did not receive anything.
- Nothing closes the window explicitly. It lapses at
closes_atand the successor sells to everyone from then on, with no further event. - A successor with no
presale_hoursset produces no presale and no event.
Related events
pass_series.completed: each holder reaching the end of the finished season.plan.deactivated: the finished season coming off sale, withreason: series_exhausted.pass_series.purchased: a holder taking up the offer.
Ability to subscribe
A token needs this to subscribe an endpoint to the event.
Header Parameters
t=<unix seconds>,v1=<hex>: the HMAC-SHA256 of "<t>.<raw body>" under the endpoint's secret. Verify it before acting, and refuse a t more than 300 seconds from now. During a secret rotation a v0= signature under the previous secret may precede v1=.
The event's ULID, bare. The envelope's id is the same ULID prefixed evt_, so strip the prefix before comparing. Deduplicate on it: a retry carries the same id.
The event name, the same as the envelope's type.
Always application/json.
Always Subscriby-Webhooks/1.0.
Payload
JSONWhat Subscriby posts to your endpointapplication/json
The signed JSON envelope posted to your endpoint.
The envelope every event is delivered in.
Responses
2XXAny success status
Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.
defaultAny other status
Any other status, a connection failure, or no answer within 30 seconds counts as a failed attempt. The delivery is retried 8 times, after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 1 day, 3 days; the last failure dead-letters it, and it can be retried from the dashboard or POST /v1/webhook-deliveries/{delivery}/retry. After 20 consecutive failures the endpoint is paused until it is resumed.
Related
How is this guide?