Tokens & auth
Client join tokens authorize a user to enter a specific room with a defined role and expiry. Mint them with REST Basic Auth on your server; pass only the JWT to the Web SDK or native client.
Scoped JWTs
Each token is bound to one roomId, one identity, and one role — not a global API key.
Configurable TTL
From 60 seconds to 24 hours. Default one hour. Re-issue for long sessions.
Role enforcement
Media plane enforces publish/subscribe rights for host, participant, and viewer.
No REST secrets in clients
Browsers and mobile apps only ever receive join tokens minted by your backend.
3600s
1 hour
60–86400
seconds
3
host · participant · viewer
64
characters
Authentication
Authorization: Basic <token>. Auth guide →Credential separation
- Basic Auth token (dashboard) → server only → create rooms, issue tokens, end/delete rooms.
- Join token (this endpoint) → client SDK → connect to media SFU for one room until expiry.
- Never put the Basic Auth secret in frontend bundles, mobile apps, or public repos.
Mint a short-lived join token for a single identity. The room must be in ready or active status. Issuing a new token for the same identity does not automatically kick an existing connection unless you set session uniqueness policy in the dashboard.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| roomId | string | required | Target room (e.g. rm_8f3a2c1b0e9d). |
Request body
{"identity": "user_ama","role": "participant","ttl": 1800}
| Field | Type | Required | Description |
|---|---|---|---|
| identity | string | required | Unique user key for this join (max 64 chars). |
| role | string | required | host | participant | viewer |
| ttl | integer | optional | Seconds until token expires. Default 3600. |
Response
{"success": true,"code": "VIDEO_TOKEN_ISSUED","message": "Join token issued.","requestId": "REQ-1782001000400","timestamp": "2026-07-12T10:16:00.000Z","data": {"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb29tSWQiOiJybV84ZjNhMmMxYjBlOWQiLCJpZGVudGl0eSI6InVzZXJfYW1hIiwicm9sZSI6InBhcnRpY2lwYW50IiwiZXhwIjoxNzIwNzg4MjAwfQ.signature","roomId": "rm_8f3a2c1b0e9d","identity": "user_ama","role": "participant","ttl": 1800,"expiresAt": "2026-07-12T10:46:00.000Z","wsUrl": "wss://video.sendexa.co/rtc"}}
Code examples
curl -X POST 'https://api.sendexa.co/v1/video/rooms/rm_8f3a2c1b0e9d/tokens' \-H 'Content-Type: application/json' \-H 'Authorization: Basic YOUR_DASHBOARD_BASE64_TOKEN' \-d '{"identity": "user_ama","role": "participant","ttl": 1800}'
Roles in depth
Full publish (camera, mic, screen) and subscribe.
- Can request mute / remove via client host APIs (when enabled)
- Typical: agent, teacher, interviewer, clinician
- Usually 1 host; multiple hosts allowed if you issue them
Publish and subscribe like a normal meeting guest.
- Camera / mic / screen share (self-managed)
- Cannot end room from client privilege alone
- Typical: customer, student, candidate
Subscribe only — no uplink A/V publish.
- Ideal for webinars and silent observers
- Still counts toward maxParticipants
- Publish attempts are rejected by the SFU
| Use case | Suggested TTL | Notes |
|---|---|---|
| OTP / KYC video check | 300–900 | Short window reduces leak risk if token is logged |
| Support / telehealth | 1800–3600 | Re-issue mid-call if session may run long |
| Classroom / webinar | 7200–14400 | Or refresh tokens every hour from your app |
| Scheduled future join | — | Do not pre-mint early; create token when the user opens the join page |
Using the token in the client
// After your backend returns { roomId, token }import { Room } from '@sendexa/video-client';const room = new Room({roomId: session.roomId,token: session.token,media: { audio: true, video: true },});try {await room.connect();} catch (e) {if (e.code === 'TOKEN_EXPIRED') {// Call your backend to mint a fresh token, then reconnectconst fresh = await fetch('/api/video/refresh', { method: 'POST' }).then((r) => r.json());room.updateToken(fresh.token);await room.connect();}throw e;}
Expiry mid-session
expiresAt and call the SDK update API.- Authenticate the end user in your app before minting any token
- Authorize room access (e.g. only assigned agent + customer for that ticket)
- Map
identityto your user ID — never accept client-supplied roles blindly - Prefer HTTPS-only delivery of tokens; avoid logging full JWTs
- Treat leaked tokens as compromised — end the room or wait for TTL expiry
Common errors
| HTTP | Code | Cause |
|---|---|---|
| 400 | INVALID_TOKEN_PARAMS | Missing identity/role or invalid TTL |
| 400 | INVALID_IDENTITY | identity empty, too long, or illegal characters |
| 401 | UNAUTHORIZED | Bad Basic Auth on the REST call |
| 404 | ROOM_NOT_FOUND | roomId unknown or deleted |
| 409 | ROOM_ALREADY_ENDED | Room not accepting new tokens |
| 409 | ROOM_AT_CAPACITY | participantCount already at maxParticipants |
| 429 | RATE_LIMIT_EXCEEDED | Too many token mints — back off |
Best practices
- Mint tokens just-in-time when the user opens the join UI
- Use distinct identities so participant webhooks attribute correctly
- For webinars, issue
viewerto the audience and reservehostfor presenters - Capacity is enforced on join — minting a token does not reserve a seat until connect
- Pair with room /end when the business session completes