---
title: "Someone bought a season ticket and their whole slate was issued."
method: POST
path: "pass_series.purchased"
tags: ["pass_series.* events"]
---

# Someone bought a season ticket and their whole slate was issued.

`POST pass_series.purchased` (webhook)

### When this fires

A [Pass Series](https://docs.subscriby.net/creators/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.

### 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`.

### Related events

- `subscription.activated`: the same purchase as revenue. De-duplicate on `subscription_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.

## Headers

- `SB-Signature` string, required
- `SB-Event-Id` string, required
- `SB-Event-Name` string, required
- `Content-Type` string, required
- `User-Agent` string, required

## Payload

- object: The envelope every event is delivered in.
  - `id` string, required: A ULID unique to the event, prefixed `evt_`. Every retry carries the same id, so it is the key to deduplicate on.
  - `type` 'pass_series.purchased', required: The event name, always `pass_series.purchased` here.
  - `created_at` string, date-time, required: When the event was emitted, server time, ISO 8601.
  - `api_version` string, required: The contract version of the payload shape, `2026-05-01` today. A breaking change to a shape ships under a new version.
  - `project_id` string, uuid, nullable, required: The project the event belongs to; null for team-scoped and billing events.
  - `data` object, required: The event-specific payload. Keys that do not apply to a given emission are omitted rather than sent as null, so check for presence.
    - `subscription_id` string, uuid, required: The purchase. **De-duplicate on this** against `subscription.activated`.
    - `subscriber_id` string, uuid, required: The project user who bought.
    - `plan_id` string, uuid, required: The series plan.
    - `plan_name` string, required: Its name at purchase time.
    - `passes_total` integer, required: How many dates this purchase covers.
    - `passes_granted` integer, required: Dates newly issued to them.
    - `passes_already_held` integer, required: Dates they already held individually. Not issued twice and not charged twice.
    - `starts_at` string, date-time, required: The **whole slate's** first date start, not this buyer's first date. Always UTC.
    - `ends_at` string, date-time, required: The whole slate's last date end, not this buyer's last date. Always UTC.
    - `seat_cap` integer, nullable, required: The series seat limit, or `null` when uncapped.
    - `seats_remaining` integer, nullable, required: Seats left after this purchase, or `null` when uncapped.

## Acknowledgement `2XX`

Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.

## Other responses

- `default`: 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.

---

[API](https://skmtc.dev/memberpass/apis/subscriby-rest-api.md) · [All operations](https://skmtc.dev/memberpass/apis/subscriby-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/memberpass/apis/subscriby-rest-api/revisions/59c4df91d764?raw)
