Token Flow
Tokens are short-lived LiveKit JWTs. Your backend requests one from RTCstack, then passes it to the frontend.
Flow
1. User clicks "Join Call"
2. Your frontend → Your backend: "I want to join room X as user Y"
3. Your backend → RTCstack API: POST /v1/token
4. RTCstack → LiveKit: sign JWT with role grants
5. RTCstack → Your backend: { token, url, expiresAt }
6. Your backend → Your frontend: { token, url }
7. Frontend: createCall({ token, url }) → call.connect()RTCstack is never involved after step 5. All WebRTC signalling goes directly to LiveKit.
POST /v1/token
Request:
POST /v1/token
Content-Type: application/json
X-Api-Key: your-api-key
X-RTCstack-Timestamp: 1714000000
X-RTCstack-Signature: <hmac>
{
"roomId": "room-abc",
"userId": "user-123",
"name": "Alice",
"role": "participant"
}Response:
{
"token": "eyJhbGciOiJIUzI1NiJ9...",
"url": "wss://rtc.example.com/livekit",
"expiresAt": "2024-04-26T10:00:00.000Z"
}Roles and Grants
| Role | Publish audio/video | Subscribe | Mute others | Room admin |
|---|---|---|---|---|
host | ✅ | ✅ | ✅ | ✅ |
moderator | ✅ | ✅ | ✅ | ❌ |
participant | ✅ | ✅ | ❌ | ❌ |
viewer | ❌ | ✅ | ❌ | ❌ |
Token Refresh
For long-running sessions, use POST /v1/token/refresh:
POST /v1/token/refresh
Content-Type: application/json
{
"roomId": "room-abc",
"userId": "user-123",
"name": "Alice",
"role": "participant"
}Wire the refresh into the SDK's tokenRefresher option:
const call = createCall({
token,
url,
tokenRefresher: async () => {
const res = await yourBackend.refreshToken({ roomId, userId, name, role })
return res.token
},
})The SDK calls tokenRefresher automatically before reconnecting when it detects the token is near expiry.
Token TTL
Default TTL is 6 hours (TOKEN_TTL_SECONDS=21600). For high-security contexts, reduce this and rely on tokenRefresher for transparent renewal.
Room Creation
POST /v1/token creates the LiveKit room automatically if it doesn't exist. You don't need to call POST /v1/rooms first — it's there for explicit room management (metadata, max participants, etc.).

