A token is a short-lived pass your app uses to join a call or a chat. Your backend makes it with the API key and sends it to the app. It's the only Livqeno credential an app ever gets.
Tokens are the link between your backend and your app:
Project → API key → your backend mints a token → your app joins with it → webhooks.
Why it exists
An API key can do everything in a project. Your app needs much less, and it can't be trusted with the key, because anyone can read an app's code. A token is the small, safe version. It holds:
- one identity (the user, like
alice) - one room, or a named list of conversations
- a fixed set of permissions (what the user may do)
- an expiry time
How a token is used
1. alice clicks "Join" → your app calls YOUR backend
2. your backend checks login → asks Livqeno for a token (with the API key)
3. Livqeno returns a grant → { token, endpoint, iceServers, ... }
4. your backend returns it → to alice's app, unchanged
5. alice's app joins → Livqeno checks the token, lets her in
The whole response in step 3 is called the grant. It has the token plus the addresses the app needs. Send the whole grant to the app, not just the token.
Two kinds
| RTC token | Chat token | |
|---|---|---|
| Use it for | Video and audio calls | Chat |
| Allows | join / publish / subscribe in one room | read / send / moderate / manage in named conversations |
| Default lifetime | 600s (10 min) | 3600s (1 hour) |
| Maximum lifetime | 21600s (6h) | 21600s (6h) |
| Used with | The signaling socket, and telemetry | The chat socket, and the chat REST API |
| Made with | livqeno.tokens.create() | livqeno.chat.createToken() |
An RTC token doesn't work for chat, and a chat token doesn't work for calls. An app that does both (a call with a chat panel) gets two tokens.
What's inside
An RTC token holds the token id, the user's identity, the project, the environment, the room id and name, the permissions, and the expiry.
- Everything is signed, so nobody can change it without breaking it.
- Anyone holding the token can read it. That's fine: nothing inside is secret.
Expiry keeps it safe
Tokens are short-lived on purpose. There's no such thing as a token that never expires. If a token leaks, it stops working soon.
Make a new token each time a user joins. By default expiry only matters
when joining (and reconnecting): a user who joined with a valid token isn't
kicked out when it expires mid-call. Where the server enforces expiry, give
the SDK a refreshToken callback so long calls get a new token in place.
Revoking early:
- RTC token: call
DELETE /v1/rooms/{roomId}/rtc-tokens/{tokenId}. It can't be used to join again. A call that's already connected keeps going. See RTC authentication → Revocation. - Chat token: your backend calls
DELETE /v1/chat/tokens/{tokenId}with the API key. New connections with it then fail withTOKEN_REVOKED. A connection that's already open stays open. There's no server-SDK method or CLI command for this yet.
Example
On your backend, make a token for a viewer who can watch and listen, but not send video:
const credentials = await livqeno.tokens.create({
room: room.id,
identity: 'user-42',
permissions: { join: true, subscribe: true },
});This token can join and watch. It can't publish, and the app can't change that.
Common mistakes
| Mistake | What you'll see | Fix |
|---|---|---|
| Reusing one old token for every join | TOKEN_EXPIRED | Make a fresh token per join. |
Taking identity from the request body | Users can join as someone else | Take it from your own login session. |
Sending only token to the app, not the whole grant | Calls fail on mobile networks | Forward the whole grant, including iceServers. |
| Using an RTC token for chat | Chat rejects it | Make a separate chat token. |
Next steps
- Mint a token: the working code, in TypeScript, Python, Go, Java and PHP.
- Permissions: what each permission allows.
- Access tokens: the full API for making tokens.