Quick Use
pip install livekit-apiornpm install livekit-server-sdk- Build an authenticated POST /livekit/token endpoint in your backend
- Frontend POSTs (user_id, room), gets back {token, url}, joins with the SDK
Intro
LiveKit rooms require signed JWT access tokens — your backend signs a token with API key + secret, granting a specific identity permission to join a specific room with specific capabilities (publish, subscribe, record, data). Never sign in the browser. This is the server-side flow with Python and Node examples. Best for: any production LiveKit deployment, multi-tenant voice apps, per-user permission models. Works with: livekit-server-sdk (Python, Node, Go, Ruby, Java). Setup time: 10 minutes.
Python token endpoint (FastAPI)
from livekit import api
from fastapi import FastAPI, HTTPException, Depends
import os
app = FastAPI()
@app.post("/livekit/token")
async def issue_token(user_id: str, room: str, user = Depends(authenticated_user)):
if not can_join_room(user, room):
raise HTTPException(403, "not allowed in this room")
token = (
api.AccessToken(os.environ["LIVEKIT_API_KEY"], os.environ["LIVEKIT_API_SECRET"])
.with_identity(user_id)
.with_name(user.display_name)
.with_grants(api.VideoGrants(
room_join=True,
room=room,
can_publish=True,
can_subscribe=True,
can_publish_data=True,
))
.with_ttl(timedelta(hours=1))
.to_jwt()
)
return {"token": token, "url": os.environ["LIVEKIT_URL"]}Node token endpoint (Express)
import { AccessToken } from 'livekit-server-sdk';
app.post('/livekit/token', requireAuth, async (req, res) => {
const { userId, room } = req.body;
if (!canJoinRoom(req.user, room)) return res.status(403).send();
const at = new AccessToken(process.env.LIVEKIT_API_KEY, process.env.LIVEKIT_API_SECRET, {
identity: userId,
name: req.user.displayName,
ttl: '1h',
});
at.addGrant({
roomJoin: true,
room,
canPublish: true,
canSubscribe: true,
canPublishData: true,
});
res.json({ token: await at.toJwt(), url: process.env.LIVEKIT_URL });
});Grant cheat sheet
| Grant | Use case |
|---|---|
room_join |
Required for any participant |
can_publish |
Participant can publish audio/video |
can_subscribe |
Participant can hear/see others |
can_publish_data |
Send arbitrary chat / state via DataChannel |
room_admin |
Can mute, remove participants |
room_record |
Can start egress recording |
ingress_admin |
Can manage SIP / RTMP ingress |
Production hardening
- Short TTL — 1 hour max for end-user tokens. Browser refreshes via your endpoint.
- Per-user identity — never reuse identities.
user_idshould be your DB primary key. - Server-side only — never log secrets, never ship API_SECRET to the browser.
- Cache the SDK — instantiate once, reuse for many tokens.
FAQ
Q: Can I sign tokens in the browser? A: No — the API_SECRET would be exposed. Always sign in your backend. The browser only ever sees the resulting JWT, which has limited TTL and scoped grants.
Q: How do agents get their own token?
A: Agents authenticate the same way — your worker process signs an agent identity token (with agent=True grant) before joining the room. LiveKit Cloud agent dispatchers handle this automatically when you deploy via the LiveKit CLI.
Q: What about webhooks for participant join/leave?
A: Configure webhooks at cloud.livekit.io → Webhooks. Events: room_started, participant_joined, participant_left, track_published, egress_started. Verify the X-LiveKit-Signature header server-side.
Source & Thanks
Built by LiveKit. Licensed under Apache-2.0.