LiveKit rooms require a WebSocket URL and a participant JWT to join. This document covers how to obtain those credentials in C++ and how that relates to in-session token refresh.
These are separate mechanisms:
| Initial connect | In-session token refresh | |
|---|---|---|
| When | Before or at Room::connect |
While already connected |
| Who provides the JWT | Your app, backend, or token server | LiveKit server (signal channel) |
| C++ API | Room::connect(url, token, …) |
RoomDelegate::onTokenRefreshed (informational) |
| Rust / FFI | JWT passed once via connectAsync |
Handled internally; C++ receives a TokenRefreshed event |
Token sources are for initial connection only. After a successful connect,
the Rust core owns the session JWT. The SDK applies server-pushed token updates
internally (for reconnect). Your token endpoint is not called again unless
you invoke Room::connect again (for example after a full disconnect).
If you need the latest JWT after a server refresh, implement
RoomDelegate::onTokenRefreshed and store TokenRefreshedEvent::token in your
application.
Cross-platform background: LiveKit authentication docs.
For local development against livekit-server --dev, install
livekit-cli and mint a token:
export LIVEKIT_URL=ws://localhost:7880
export LIVEKIT_TOKEN=$(lk token create \
--api-key devkey \
--api-secret secret \
-i my-participant \
--join \
--valid-for 24h \
--room my-room \
--grant '{"canPublish":true,"canSubscribe":true,"canPublishData":true}' \
--token-only)For integration tests that need two participants, see
testing.md and the helper
script under .token_helpers/.
When you already have credentials, pass them directly — no token source required:
#include "livekit/livekit.h"
livekit::initialize(livekit::LogLevel::Info);
livekit::Room room;
livekit::RoomOptions options;
if (!room.connect(url, token, options)) {
// handle failure
}Use this for prototypes, tests, or when your app fetches the JWT outside the SDK.
Token sources are C++ helpers that call fetch() to produce a
TokenSourceResponse. Pass server_url and participant_token to
Room::connect; optional room_name and participant_name response fields are
informational metadata for application UI/logging when a token server provides
them:
#include <livekit/token_source.h>
auto source = /* EndpointTokenSource, SandboxTokenSource, CustomTokenSource, etc. */;
auto credentials = source->fetch(request_options).get();
if (!credentials ||
!room.connect(credentials.value().server_url, credentials.value().participant_token, options)) {
// handle failure
}For a fixed source such as LiteralTokenSource, call fetch() without request
options.
Room does not retain the token source after connect. Calling fetch()
again only happens when your application invokes it again.
| Base class | fetch signature |
Use when |
|---|---|---|
TokenSourceFixed |
fetch() |
Credentials are fully determined without per-call options |
TokenSourceConfigurable |
fetch(options) |
Room, identity, agent dispatch, etc. vary per connect |
| Type | Class | Typical use |
|---|---|---|
| Literal | LiteralTokenSource |
Static URL + JWT, or lazy async provider |
| Endpoint | EndpointTokenSource |
Production: HTTP token server on your backend |
| Sandbox | SandboxTokenSource |
Development: LiveKit Cloud sandbox token server |
| Custom | CustomTokenSource |
Your own async credential logic |
| Caching | CachingTokenSource |
Decorator: JWT-aware cache around a configurable source |
Static credentials you already have, or credentials loaded asynchronously (keychain, secure storage, your auth service):
#include <livekit/token_source.h>
// Static URL + JWT
auto source = livekit::LiteralTokenSource::create(url, jwt);
auto credentials = source->fetch().get();
if (!credentials ||
!room.connect(credentials.value().server_url, credentials.value().participant_token, options)) {
return 1;
}
// Async provider (same contract as JS TokenSource.literal(async () => …))
auto source2 = livekit::LiteralTokenSource::create([]() {
return std::async(std::launch::async, [] {
livekit::TokenSourceResponse details;
details.server_url = /* ... */;
details.participant_token = /* ... */;
return livekit::Result<livekit::TokenSourceResponse, livekit::TokenSourceError>::success(details);
});
});Recommended for production. Keeps API keys on your server; the SDK POSTs (or GETs) to your token endpoint with optional room, participant, and agent fields.
Request and response formats follow the standard token server contract.
livekit::TokenRequestOptions request;
request.room_name = "my-room";
request.participant_identity = "user-123";
livekit::TokenEndpointOptions endpoint_options;
endpoint_options.method = "POST"; // default; set to "GET" if your server requires it
endpoint_options.headers["Authorization"] = "Bearer your-api-token";
auto source = livekit::EndpointTokenSource::create(
"https://your-backend.example.com/token",
std::move(endpoint_options));
auto credentials = source->fetch(request).get();
if (!credentials ||
!room.connect(credentials.value().server_url, credentials.value().participant_token, options)) {
return 1;
}Uses the LiveKit Cloud sandbox token server. Do not use in production.
auto source = livekit::SandboxTokenSource::create("your-sandbox-id");
livekit::TokenRequestOptions request;
request.agent_name = "my-agent"; // optional agent dispatch
auto credentials = source->fetch(request).get();
if (!credentials ||
!room.connect(credentials.value().server_url, credentials.value().participant_token, options)) {
return 1;
}See sandbox token server docs.
Integrate an existing auth system without adopting the standard endpoint wire format:
auto source = livekit::CustomTokenSource::create(
[](const livekit::TokenRequestOptions& options)
-> std::future<livekit::Result<livekit::TokenSourceResponse, livekit::TokenSourceError>> {
std::promise<livekit::Result<livekit::TokenSourceResponse, livekit::TokenSourceError>> promise;
livekit::TokenSourceResponse details;
details.server_url = /* ... */;
details.participant_token = /* encode options into your JWT ... */;
promise.set_value(
livekit::Result<livekit::TokenSourceResponse, livekit::TokenSourceError>::success(details));
return promise.get_future();
});Wraps another configurable source and reuses a cached JWT when options match
and the token is still valid. Call invalidate() to drop the cached credentials
so the next fetch() re-queries the inner source — useful when calling
Room::connect again, not for server-pushed refresh during an active
session. cachedResponse() returns the currently cached credentials, if any.
auto inner = livekit::EndpointTokenSource::create("https://your-backend.example.com/token");
auto source = livekit::CachingTokenSource::create(std::move(inner));
auto credentials = source->fetch(request).get();
if (!credentials ||
!room.connect(credentials.value().server_url, credentials.value().participant_token, options)) {
return 1;
}During an active session, the LiveKit server may push an updated JWT over the signal connection so the client can reconnect if the original join token expires. The Rust core stores and applies this token automatically.
C++ surfaces an optional notification:
class MyDelegate : public livekit::RoomDelegate {
public:
void onTokenRefreshed(livekit::Room&, const livekit::TokenRefreshedEvent& ev) override {
// ev.token — latest JWT the SDK is using internally
}
};This event is informational. It does not invoke your token source or token endpoint. Automatic reconnect/resume while connected uses the internally stored JWT only.