client.realtime.connect() and file uploads.
Why client tokens
- Safe to send to browsers and mobile apps
- Short-lived (configurable TTL, 1–3600 seconds, default 60s; see Token lifetime)
- Optional model scoping (restrict which models the key can access)
- Optional origin scoping (restrict which web origins can use the key — browser-enforced)
- Limited scope (cannot create new tokens)
- Signed, so the gateway verifies them offline and connecting doesn’t wait on a key lookup
- Expiration blocks new connections, but does not disconnect active realtime sessions
End-to-end flow
1
Create token on your backend
Your backend uses your permanent API key to create a client token.
2
Return token to the frontend
Return
apiKey and expiresAt from your backend endpoint.3
Connect with the client token
Your frontend passes the token to the SDK, minted right before
connect() or fetched per dial through apiKeyProvider. See Token lifetime.Options
All options are optional. Without options, tokens use a 60-second TTL and are unrestricted.
Constraints object:
expiresIn vs maxSessionDuration — these control different things.
expiresIn sets how long the token can be used to start new connections. Once a realtime session is established, the token’s expiration does not terminate it.
maxSessionDuration caps how long an individual realtime session can remain active, regardless of token expiration.
Use both together for full control: e.g. a 5-minute token window with a 2-minute max per session.Token lifetime
Client tokens expire 60 seconds after minting by default;expiresIn sets the TTL from 1 to 3600 seconds. A token minted on page load is usually dead by the time the user has granted camera access and pressed start, and a reconnect with the same token is refused too.
Keep the token fresh in one of two ways:
- apiKeyProvider (recommended)
- Mint right before connecting
Pass a function instead of a key. The SDK calls it right before every
connect(), connect retry, reconnect and subscribe(), so each dial carries a token minted for it:apiKeyProvider wins over apiKey for realtime; the HTTP APIs (process, queue, files, tokens) still use apiKey or proxy. A provider rejection during connect() rejects the call with that error; during a reconnect it is retried like any other dial failure.Expired tokens fail fast
Before every realtime dial the SDK reads the token’sexp claim (5-second clock-skew tolerance) and rejects an expired one with TOKEN_EXPIRED without opening a socket:
connect()andsubscribe()reject with{ code: "TOKEN_EXPIRED", message, data: { expiresAt, expiredSecondsAgo } }.- On a reconnect, the session emits an
errorevent withTOKEN_EXPIREDand moves todisconnectedwithout retrying.
Expiry only gates new dials. A realtime session that is already connected keeps running after its token expires. Use
constraints.realtime.maxSessionDuration to cap how long a session can run.Model scoping
PassallowedModels to restrict which models a token can be used with. The bouncer verifies model permissions when the client connects — if the model isn’t in the allowed list, the connection is rejected.
Tokens created without allowedModels are unrestricted and work with any model.
Origin scoping
PassallowedOrigins to pin a token to a specific list of web origins. When the token is later used to open a realtime session, the connection is accepted only if the browser-issued WebSocket Origin header matches one of the listed origins. Mismatched or missing origins are rejected with close code 1008 and {"type":"error","error":"Origin not allowed"}.
Each entry must be a canonical origin so it compares byte-for-byte to what browsers send. The mint endpoint enforces this and returns a 400 (with the canonical form in the message) when input doesn’t match:
- scheme
http://orhttps:// - lowercase scheme and host
- no trailing slash, path, query, or fragment
- no userinfo (credentials)
- no default port (
:443for https,:80for http) - max 253 chars per entry, max 20 entries per token
Tokens created without
allowedOrigins are unrestricted (no origin enforcement).
Defense-in-depth, not hermetic. Origin enforcement is browser-driven: it materially raises the cost of stolen-token replay from a different web origin, but does not protect against an attacker who controls a non-browser HTTP client and can spoof the
Origin header. Treat it as one layer alongside short TTLs and model scoping, not a sole boundary.Backend examples
Frontend example
Rotation strategy
- Mint a token right before each connect, or let
apiKeyProviderdo it for every connect and reconnect. Never mint on page load - Use shorter TTLs (e.g. 60s) for tighter security; use longer TTLs (e.g. 300–600s) when you mint ahead of connecting
- An expired token is refused before dialling (
TOKEN_EXPIRED). Mint a new one instead of retrying with the old - Do not persist client tokens in local storage
- A client token cannot be revoked before
expiresAt; use a short TTL where fast cut-off matters - Revoke permanent keys in dashboard if you suspect leakage