Mint Your Own Playback Tokens with Signing Keys
For bring-your-own-player setups: create a signing key in the dashboard and mint playback tokens on your own backend, with no per-viewer API call. Token format, code example, limits, and revocation.
Who This Is For
If you embed your stream with the hosted iframe, you never touch tokens and can skip this page. This guide is for bring-your-own-player (BYO) integrations: you run hls.js, dash.js, AVPlayer, or ExoPlayer on your own site or app, and your backend authorizes each viewer.
Authorization works with signing keys: your backend signs a short-lived JWT with a key you created in the dashboard, and our edge verifies it on every request. There is no per-viewer call to us, no shared infrastructure in your critical path, and your viewers get the full adaptive-bitrate stream. See Stream to Your Own Website for the embed basics.
BYO playback requires the Business plan plus the bring-your-own-player flag on your workspace, which support enables during onboarding.
Create a Signing Key
Open your playout, go to the Embed tab, and set the access mode to Signed (token required).
A Signing keys section appears. Name the key after the system that will hold it ("production backend") and create it.
Copy the key id (
ck_...) and the secret. The secret is shown exactly once. Store it wherever your other production credentials live.
Never ship the secret to a browser or a mobile bundle. Tokens are minted server-side; the player only ever sees the finished token.
The Token
A playback token is a standard HS256 JWT with five claims:
Claim | Value |
|---|---|
| your embed's public id (the hex string in your player link) |
| now, in unix seconds |
| expiry, at most 2 hours after |
| your key id ( |
| a fresh random session id per viewer: 8-64 lowercase hex characters |
Append it to the stream URL and hand that to your player:
https://stream.playout.video/<publicId>/master.m3u8?token=<jwt>
A minimal Node implementation, no libraries needed:
import { createHmac, randomBytes } from "node:crypto"
const KID = "ck_..." // from the dashboard
const SECRET = process.env.PLAYOUT_SIGNING_SECRET // shown once at creation
const PUBLIC_ID = "..." // from the Embed tab
export function mintPlaybackToken() {
const now = Math.floor(Date.now() / 1000)
const enc = (v) => Buffer.from(JSON.stringify(v)).toString("base64url")
const head = enc({ alg: "HS256", typ: "JWT" })
const body = enc({
sub: PUBLIC_ID,
iat: now,
exp: now + 7200, // max 2 hours
kid: KID,
jti: randomBytes(16).toString("hex"), // one per viewer session
})
const sig = createHmac("sha256", SECRET).update(`${head}.${body}`).digest("base64url")
return `${head}.${body}.${sig}`
}
Any JWT library produces the same result. With jsonwebtoken:
jwt.sign({ sub: PUBLIC_ID, jti }, SECRET, { algorithm: "HS256", expiresIn: "2h", keyid: KID })
The Rules the Edge Enforces
Your token is checked at our CDN edge on every request. Four rules matter to your integration:
One token per viewer. The
jtiis the viewer session: it drives the live viewer count, usage metering, and the signal that keeps the encoder running. If you hand one token to your whole audience, everyone counts (and gets capped) as a single viewer.Two-hour ceiling. Tokens that claim a longer life are rejected outright, not trimmed. Mint a fresh token and swap your player's source before expiry; an expired token turns the next playlist reload into a 401.
Your keys open only your streams. A signing key belongs to your workspace and cannot authorize playback of anyone else's embed, and nobody else's key can authorize yours.
Access settings stay in the dashboard. The embed's access mode and domain allowlist are enforced from your saved settings, not from anything inside the token. Minting your own tokens never widens who can watch.
Plan viewer limits still apply: when a stream is at its concurrent-viewer cap, new sessions get a 429 on their first manifest request. Show a "stream is full" state and retry later. Viewers already watching are never cut off.
Revoking a Key
Revoke a key from the same Signing keys section. Every token it signed stops working within about a minute, which makes revocation the fastest kill switch we offer: rotating the embed link invalidates all URLs everywhere, while revoking a key surgically cuts off one backend. Create the replacement key first if you are rotating credentials without downtime; you can hold up to five active keys.
Troubleshooting
Symptom | Likely cause |
|---|---|
401 on every request | wrong secret, |
401 after ~1 minute | the key was revoked |
403 | the embed belongs to a different workspace than the key, or the viewer's site is not on the domain allowlist |
429 on first load | the stream is at its plan's concurrent-viewer cap |
Player loads the manifest but segments 401 | your player is not sending credentials; enable cookies as described in the BYO player guide |
Playback behavior (cold starts, offline handling, DVR) is identical to API-minted tokens, so everything in the embed guides applies unchanged.