A chat token is a short-lived pass that lets one user's app connect to chat. Your backend makes it with the API key and sends it to the app. The app never sees the API key.
Where this fits: your backend creates a conversation and adds members. Then, each time a logged-in user opens the chat, your backend makes them a chat token (this page). The app connects with it. For the bigger picture, see Access tokens.
Make a token
Do this on your backend, in an endpoint your app calls after the user
logs in. Take userId from your own login session, never from the
request body. Otherwise anyone could chat as anyone.
const token = await livqeno.chat.createToken({
userId: 'alice',
conversations: [conversation.publicId],
expiresIn: 3600, // optional, seconds
});from livqeno import CreateChatTokenParams
token = livqeno.chat.create_token(
CreateChatTokenParams(user_id="alice", conversations=[conversation["publicId"]], expires_in=3600)
)You get back a grant: the token itself, plus chatUrl and apiUrl
(where the app connects), scopes, conversations, tokenId, and
expiresAt. Send the whole grant to your app unchanged.
| Option | What it does | Default |
|---|---|---|
userId | Your id for the user. Becomes the sender of every message they send. | Required |
conversations | Limit the token to these conversations. | Every conversation the user is a member of |
expiresIn | How long the token lasts, in seconds. | 3600 (1 hour). Maximum 6 hours. |
scopes | Narrow what the user may do (see below). | Everything their role allows |
A chat token and an RTC token are separate. They use different keys, and neither works for the other. If one leaks, the other is still safe.
Two independent checks
Every chat action passes two checks:
- The token says who you are and what you may do. It holds: a project, a user id, an expiry, an optional list of conversations, and a set of scopes.
- Membership says which conversations you belong to, and your role in
each one:
MEMBER,MODERATOR, orADMIN.
Why two? The token is made once and lives up to hours. Membership can change at any moment. Checking both means that removing someone from a conversation works immediately, even though their token hasn't expired.
Your role decides which scopes you can have. A token can remove scopes from a role, but never add them. So giving out a limited token is always safe.
| Scope | Lets you | Roles that have it |
|---|---|---|
chat:read | Read messages, history, presence, and read receipts | All |
chat:send | Send, edit, and delete your own messages; react; type | All |
chat:moderate | Delete anyone's message | MODERATOR, ADMIN |
chat:manage | Change the conversation's settings | ADMIN |
Example: Bob is a MODERATOR, but you want a read-only "observer" view.
Make his token with scopes: ['chat:read']. That token can't send or delete,
even though his role could.
Keeping users connected past expiry
A token expires after expiresIn. When it does, the connection closes and
the app must connect again with a new token from your backend.
To make this seamless, pass onTokenExpiring when you create the client. The
SDK calls it shortly before expiry. Fetch a new token from your backend and
return it; the SDK reconnects with it. Without it, the socket just closes at
expiry.
Revocation
You can cancel a token before it expires, for example when a user logs out or you suspect it leaked. From your backend:
DELETE /v1/chat/tokens/{tokenId}tokenIdcomes from the token response.- This call needs your project API key, so make it from your backend.
- It always succeeds. Chat tokens aren't stored, so there's no
404for an unknown id. - The block lasts for
CHAT_TOKEN_MAX_TTL_SECONDS(6 hours by default), no matter whatexpiresInthe token had.
What the server never trusts from a browser
Anything sent from a browser or phone can be changed by the user. So some things only ever come from the signed token or from your backend:
- The sender always comes from the signed token, never from the request body.
systemandeventmessages need a project API key. A user can't fake an official announcement.
See Moderation for how chat:moderate is enforced.
Common errors
| Problem | Cause | Fix |
|---|---|---|
chat:send missing | The user's role doesn't allow it (rare; MEMBER has it), or the token removed it. | Check the role in Members and the token's scopes. |
| Token works for one conversation but not another | The token's conversations list didn't include it. | Leave out conversations to allow every conversation the user is in, or add it to the list. |
ROOM_NOT_FOUND right after connecting | The user isn't a member. The grant shows it under pendingMembership. | Call addMember before making the token. |
TOKEN_EXPIRED, socket closes with 4440 | The token's time is up. | Fetch a new one from your backend, or use onTokenExpiring. |
TOKEN_REVOKED | You revoked this token. | Make a new token if the user should still have access. |
Production tips
- Make a new token for each login session. Don't reuse one long-lived token.
- Keep
expiresInshort (the 1-hour default is fine) and refresh withonTokenExpiring. A short token limits the damage if one leaks. - If you have your own role check, pass its result as
scopes. Scopes can only reduce access, so this is always safe.
Next steps
- Quickstart: see a token used end to end.
- Members: how roles are given.
- Authentication → Tokens: the token format shared by RTC and Chat.
API reference
livqeno.chat.createToken() (TypeScript) / livqeno.chat.create_token()
(Python) / client.Chat.CreateToken() (Go) / livqeno.chat().createToken()
(Java) / $client->chat->createToken() (PHP). See TypeScript SDK,
Python SDK, Go SDK, Java SDK and PHP SDK.