Skip to content

Integration: Catena tickets

This page is for the developer of a storefront or billing system — the place where you sell access. Instead of answering a callback on every viewer, you create the session ahead of time and hand the viewer a ticket. The streamer admits the ticket on its own: it checks the signature, expiry, and scope locally, with no call to your systems on the playback path.

The payoff: playback no longer depends on your storefront's speed or availability, channels start and switch instantly, and there is no per-viewer callback to keep scaled. Your systems are needed at the moment you sell access — not while the viewer watches.

Create a grant

When you sell access, call the management API to create a grant. Catena signs a ticket (a JWT) and returns it — the token is handed back only at creation; the registry keeps the grant, not the ticket.

Authenticate the call the way you authenticate any management API request — with the admin login and password. So your storefront never has to hold full admin credentials, you can instead configure a dedicated issuer key in central — a bearer token whose only power is to issue and manage grants — and send it as Authorization: Bearer <issuer-key>.

POST /play-sessions/grants
{
  "streams": ["news", "arena"],
  "user_id": "subscriber-4821",
  "ttl_secs": 14400,
  "max_concurrent": 2,
  "bind_ip": "203.0.113.0/24",
  "note": "family plan"
}
Field Meaning
streams Which channels the ticket may play — its scope
user_id The account the ticket belongs to; groups the viewer's screens
ttl_secs Ticket lifetime; default 4 hours. exp is a hard end — a session cannot outlive it (players do not refresh the token)
max_concurrent Simultaneous screens; default 1, null for unlimited
bind_ip Optional IP or CIDR the ticket is useless outside of
note Optional free-text label

The reply carries the grant and the token. Hand that token to the player, which presents it on the play URL as ?token=<jwt> (or in an Authorization: Bearer header). Nothing else is required of the viewer — the streamer takes it from there.

Screens

max_concurrent limits how many screens actively consume the ticket at once — a household with two TVs, say. Go over the limit and the oldest screen is dropped; the newest wins. You can change the limit later without reissuing the ticket, because it lives in the registry, not in the signed token:

PATCH /play-sessions/grants/{jti}
{ "max_concurrent": 3 }

Revoke

Cancel a grant and every streamer drops its viewers within seconds and refuses the ticket from then on — the cancellation travels to the streamers over a revocation feed, not by waiting for the ticket to expire. There is no reauthorization for ticket sessions; revocation is the single channel that ends them early.

Roaming

A viewer whose address changes — a phone leaving Wi-Fi for mobile — is admitted afresh on the new address, offline, while the old session dies by idle timeout. It counts as one journey, not a new charge, grouped by the grant. If you set bind_ip, roaming stays within that range.

List and status

GET /play-sessions/grants lists grants and their status — issued (handed out, not yet played), played (used at least once), revoked, expired — filterable by user_id and status. This is the record of what you sold and whether it was used.

Migrate at your own pace

Tickets and an existing backend run side by side — tickets for some viewers, the callback for others — so you can move audience by audience. The end state is the switch opaque_tokens: false on the auth policy: once set, any non-ticket token is refused outright, without consulting lists or a backend, and the callback path is closed for good.

Next steps