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

FieldTypeNotes
idstring ULIDUnique event id, prefixed evt_. Use for idempotent processing.
typestringAlways pass_series.purchased for this event.
created_atISO 8601 timestampServer-side emission time.
api_versionstringWebhook API contract version.
project_idstring UUIDProject this event belongs to.
data.subscription_idstring UUIDThe purchase. De-duplicate on this against subscription.activated.
data.subscriber_idstring UUIDThe project user who bought.
data.plan_idstring UUIDThe series plan.
data.plan_namestringIts name at purchase time.
data.passes_totalintegerHow many dates this purchase covers.
data.passes_grantedintegerDates newly issued to them.
data.passes_already_heldintegerDates they already held individually. Not issued twice and not charged twice — see below.
data.starts_atISO 8601 timestampThe whole slate's first date start — not this buyer's first date. Always UTC.
data.ends_atISO 8601 timestampThe whole slate's last date end — not this buyer's last date. Always UTC.
data.seat_capinteger | nullThe series seat limit, or null when uncapped.
data.seats_remaininginteger | nullSeats 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-zero passes_already_held is normal, not an error.
  • passes_total counts 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_at and ends_at are the plan's cached slate span, so on a mid-season purchase starts_at can precede every date the buyer actually holds. Only the passes_* counts are buyer-scoped.
  • A series absorbing new dates later does not re-emit this. That is pass_series.leg_added.

How is this guide?

On this page

Subscriby is a product designed by you — for you.

No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request