Skip to content

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:

http
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:

json
{
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "url": "wss://rtc.example.com/livekit",
  "expiresAt": "2024-04-26T10:00:00.000Z"
}

Roles and Grants

RolePublish audio/videoSubscribeMute othersRoom admin
host
moderator
participant
viewer

Token Refresh

For long-running sessions, use POST /v1/token/refresh:

http
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:

ts
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.).