Appearance
Paid access
You sell the pass in your own checkout, with your own payment provider. Vabu decides who gets to press play. The link between the two is one call from your server: grant an entitlement after the payment clears.
An entitlement says that one customer may use one resource, for a period, within limits. The resource is a live stream (type ls) or an asset (type ast).
Grant access after a payment
Make this call from your server when your payment provider confirms the money. Use a secret key, never a browser key.
http
POST /v1/access/entitlements
Authorization: Bearer vk_live_...
Idempotency-Key: grant-pay-8842
{
"customer": "user_42",
"resource": { "type": "ls", "id": "ls_01J9ZQ4M8T3K6Y2E5W7N0B1C9D" },
"source": "tenant_grant",
"source_ref": "pay-8842",
"starts_at": "2026-10-16T16:00:00Z",
"ends_at": "2026-10-17T04:00:00Z",
"limits": {
"max_devices": 2,
"max_concurrent_sessions": 1,
"concurrency_policy": "replace_oldest"
},
"metadata": { "ticket": "VIP" }
}You get 201 and an entitlement with "status": "active".
| Field | Meaning |
|---|---|
customer | Your own id for the buyer. It must match the sub of the customer token you sign for them. If you have no accounts, send contact_hash instead: a hash of a verified phone or email, salted with your own secret. |
resource | What they may watch. ls for a live stream, ast for a video asset. |
source | Why they have access, for your reports: tenant_grant (the default), payment, comp, subscription, membership_tier, ticket or promotion. |
source_ref | The payment, order or grant behind it. Your payment id is a good value. |
starts_at, ends_at | The window. Both need a time zone offset, such as Z. Leave them out for no limit. |
limits | Device and viewing rules. See below. |
external_id, metadata | Your own references. |
Make it safe to repeat
Payment providers send the same confirmation more than once, so your handler will run more than once. This is safe. Two things protect you:
- The
Idempotency-Keyheader is required. Send the same key and you get the stored response back. - With a
source_ref, a second grant for the same source, reference, customer and resource changes nothing and returns the existing entitlement with200.
A repeat of a revoked grant is refused with 409 entitlement_revoked. If you want to give access again, use a new source_ref.
Check before you show a button
POST /v1/access/check answers whether a customer may watch right now:
json
{ "data": { "allowed": false, "reason": "expired", "entitlement": { "id": "ent_...", "status": "expired" } } }reason is null when allowed. Otherwise it is no_entitlement, expired, revoked, suspended, not_started, country_not_allowed or domain_not_allowed. Use it to choose between "Watch" and "Buy a pass". The answer follows every change at once, including a revoke.
Limit devices
The limits object decides what a second screen does.
| You want | Set |
|---|---|
| One screen at a time, the latest wins (a viewer who picks up their phone moves the stream there) | max_concurrent_sessions: 1, concurrency_policy: "replace_oldest" |
| One screen at a time, the first holds the seat | max_concurrent_sessions: 1, concurrency_policy: "reject_new" |
| A household with up to 2 devices in all | max_devices: 2 |
| A pass that works for 3 viewings | max_views: 3 |
| Only viewers in Kenya and Uganda | allowed_countries: ["KE", "UG"] |
| Only on your own site | allowed_domains: ["example.com"] |
Your page sends a device_id when it opens a session. Use one stable id for each browser or phone, for example one saved in local storage. Vabu counts different ids as different devices.
With replace_oldest, the older session ends at its next heartbeat, within 30 seconds, and its player gets 403 session_ended with the reason replaced_by_newer_session. Show "You are watching on another device". With reject_new, the second device is refused with 409 concurrency_limit_reached.
How sessions and heartbeats work is in playback and sessions.
Revoke access
Take access away when a payment is refunded or reversed:
http
DELETE /v1/access/entitlements/ent_...This is final. The response is the entitlement with "status": "revoked". For a pause you can undo, use POST /v1/access/entitlements/{id}/suspend and .../resume.
What happens next:
- The next
checksays no, at once. - A new session is refused at once.
- A session that is playing ends at its next heartbeat.
- The playback link already in the viewer's player works until its token runs out, at most about 11 minutes. See signed playback.
Find a customer's entitlements with GET /v1/access/entitlements?customer=user_42, or by your own id with GET /v1/access/entitlements/external/{external_id}.
Sell replays
A replay is an asset, so it takes an entitlement of its own, with resource type ast.
- Add the recording as an asset. See assets and recordings. Wait for
streams.asset.ready. - When someone buys the replay, grant an entitlement on the asset, with an
ends_atthat matches what you sold, for example 7 days. - To include the replay in a live pass, grant two entitlements in the same handler: one on the stream and one on the asset, with a later
ends_aton the asset.
The viewer opens a session on the asset exactly as on a live stream. Because the replay entitlement has its own limits, you can sell a replay at one screen per purchase.
Tickets that include a stream
If you sell tickets with Vabu Events, listen for events.order.paid and grant the stream in your handler, with source: "ticket", the order code as source_ref and your own id for the buyer. See how the products work together.
Keep your records in step
Every grant, revoke, suspend, resume and expiry sends an event: access.entitlement.granted, revoked, suspended, resumed, expired and extended. Use them to update your own database. See stream events.