Skip to content

Playback and access sessions ​

A viewer needs two things to watch a protected stream: an entitlement (the right to watch) and a session (one viewer on one device, watching now). You create the entitlement after a payment. The session is opened every time someone presses play.

Open a session ​

Open a session for a live stream or an asset with POST /v1/streams/playback_tokens. It does the same as POST /v1/access/sessions and adds the playback link.

From your server, name the customer:

http
POST /v1/streams/playback_tokens
Authorization: Bearer vk_live_...

{ "resource": "ls_...", "customer": "user_42", "device_id": "living-room-tv" }

From a browser, send a publishable key and the customer token, and leave customer out. The customer is the token's sub.

http
POST /v1/streams/playback_tokens
Vabu-Publishable-Key: vpk_live_...
Authorization: Bearer <customer token>

{ "resource": "ls_...", "device_id": "browser-1" }

Vabu checks the entitlement and its limits, applies the device rules and returns the session:

json
{
  "data": {
    "id": "as_01J9ZQ7P2H5V8X1A4D6F9G3JKM",
    "object": "access_session",
    "customer": "user_42",
    "device_id": "browser-1",
    "state": "active",
    "expires_at": "2026-10-11T08:01:30Z",
    "heartbeat_interval": 30,
    "grant": {
      "sources": [{ "provider": "...", "url": "https://.../....m3u8?token=..." }],
      "failover": { "max_errors": 3, "window_seconds": 20 },
      "heartbeat_interval": 30,
      "token_expires_at": "2026-10-11T08:11:30Z"
    }
  }
}

device_id is a string your app makes up and keeps for each device, for example one saved in local storage. The same device_id opening again replaces its own earlier session.

The grant ​

grant.sources is an ordered list of playback links. Play the first. If the player reports failover.max_errors fatal errors within failover.window_seconds, switch to the next. There is one source today, so the list has one item. Code the switch anyway, so more sources work without a change.

A public stream has a plain link with no token, and token_expires_at is null.

Keep the session alive ​

Send a heartbeat every heartbeat_interval seconds (30):

http
POST /v1/access/sessions/as_.../heartbeat

A session that misses two heartbeats ends, about 90 seconds after the last one. A heartbeat returns the session with a fresh grant.

The link in your player expires. Its token lasts for the session's length plus 10 minutes, so about 11 minutes. Each heartbeat returns a new link with a new token. Swap the player to the newest link every 5 minutes or so. With hls.js that is hls.loadSource(newUrl). The quickstart shows it. A viewer who is watching a live stream rejoins at the live edge when you swap.

End a session when the viewer leaves:

http
DELETE /v1/access/sessions/as_...

Device and session limits ​

The entitlement's limits decide what a second device does. See paid access.

LimitEffect
max_devicesThe number of different device_id values that may be used. A new device beyond it gets 403 device_limit_reached.
max_concurrent_sessions with concurrency_policy: "reject_new"The new session is refused with 409 concurrency_limit_reached.
max_concurrent_sessions with concurrency_policy: "replace_oldest"The new session opens. The oldest one ends, and its next heartbeat gets 403 session_ended. The latest device wins.
max_viewsThe number of sessions the customer may open in total. Beyond it, 403 view_limit_reached.
allowed_countriesViewers must be in these countries.
allowed_domainsThe page must be on one of these hosts. example.com does not cover www.example.com.

A browser cannot state its own country or domain. With a publishable key, the domain is the host of the request's Origin header and the country is the ctry claim your server put in the customer token. An entitlement that limits countries or domains refuses a browser request that carries neither.

When a session ends ​

A heartbeat on an ended session returns 403 session_ended, and error.details.reason says why.

ReasonMeaning
replaced_by_newer_sessionAnother device took over under replace_oldest.
missed_heartbeatsThe page stopped sending heartbeats, for example the tab was closed or went offline.
entitlement_inactiveYou revoked or suspended access, or it expired.
clientThe viewer or your app ended it.

Show a clear message for each. "You are watching on another device" is much kinder than a frozen picture.

Errors when opening a session ​

CodeHTTPMeaning
access_denied403No active entitlement. error.details.reason says why.
device_limit_reached403max_devices is used up.
view_limit_reached403max_views is used up.
concurrency_limit_reached409Another session is open and the policy is reject_new.
stream_disabled409The live stream is switched off.
asset_not_ready409The asset is still processing.
no_playback_id409The stream or asset has no way to be played yet.
invalid_customer_token401The browser's token was rejected. See customer tokens.
product_not_enabled403Streams is not switched on for your account.

A refused open does not use a view or a device slot. All codes are on the error codes page.

Vabu OS by Vabu Group