Skip to main content

Custom token generation

Use a pre-existing token generation mechanism with LiveKit SDKs.

Overview

If you already have a way of generating LiveKit tokens and don't want to use sandbox token generation or endpoint token generation, you can use a custom TokenSource. This allows you to provide your own token generation mechanism, with automatic token fetching, caching, and refreshing included.

Agent dispatch

When using a custom TokenSource with agent applications, agent information (like agentName and agentMetadata) is available in the options parameter. If your custom token generation function calls a standard token endpoint, you should package this agent information into room_config before sending the request. The endpoint will then pass room_config directly to the access token builder. See the examples below for platform-specific syntax.

Caching tokens

TokenSource.custom will refetch cached tokens when it expires, or when the input parameters passed into the fetch method changes.

If you'd like to avoid the automatic caching behavior or handle it manually, see TokenSource.literal.

Use a custom TokenSource

This example shows how to use a custom TokenSource to connect to a LiveKit room.

import { Room, TokenSource } from 'livekit-client';
const LIVEKIT_URL = "<your LiveKit server URL>";
// Create the TokenSource
const tokenSource = TokenSource.custom(async (options) => {
// Run your custom token generation logic, using values in `options` as inputs
// `options` includes: roomName, participantName, agentName, roomConfig, etc.
// For agent applications, if calling a standard endpoint, package agent info into roomConfig
const participantToken = await customTokenGenerationFunction(
options.roomName,
options.participantName,
options.agentName, // Available when using Session APIs with agentName
/* etc */
);
return { serverUrl: LIVEKIT_URL, participantToken };
});
// Generate a new token (cached and automatically refreshed as needed)
const { serverUrl, participantToken } = await tokenSource.fetch({ roomName: "room name to join" });
// Use the generated token to connect to a room
const room = new Room();
room.connect(serverUrl, participantToken);
import { TokenSource } from 'livekit-client';
import { useSession, SessionProvider } from '@livekit/components-react';
const LIVEKIT_URL = "<your LiveKit server URL>";
// Create the TokenSource
//
// If your TokenSource.custom relies on other dependencies other than `options`, be
// sure to wrap it in a `useMemo` so that the reference stays stable.
const tokenSource = TokenSource.custom(async (options) => {
// Run your custom token generation logic, using values in `options` as inputs
// `options` includes: roomName, participantName, agentName, roomConfig, etc.
// For agent applications, if calling a standard endpoint, package agent info into roomConfig
const participantToken = await customTokenGenerationFunction(
options.roomName,
options.participantName,
options.agentName, // Available when using Session APIs with agentName
/* etc */
);
return { serverUrl: LIVEKIT_URL, participantToken };
});
export const MyPage = () => {
const session = useSession(tokenSource, { roomName: "room name to join" });
// Start the session when the component mounts, and end the session when the component unmounts
useEffect(() => {
session.start();
return () => {
session.end();
};
}, []);
return (
<SessionProvider session={session}>
<MyComponent />
</SessionProvider>
)
}
export const MyComponent = () => {
// Access the session available via the context to build your app
// ie, show a list of all camera tracks:
const cameraTracks = useTracks([Track.Source.Camera], {onlySubscribed: true});
return (
<>
{cameraTracks.map((trackReference) => {
return (
<VideoTrack {...trackReference} />
)
})}
</>
)
}
import LiveKitComponents
let LIVEKIT_URL = URL(string: "<your LiveKit server URL>")!
public struct MyTokenSource: TokenSourceConfigurable {}
public extension MyTokenSource {
func fetch(_ options: TokenRequestOptions) async throws -> TokenSourceResponse {
// Run your custom token generation logic, using values in `options` as inputs
// `options` includes: roomName, participantName, agentName, roomConfig, etc.
// For agent applications, if calling a standard endpoint, package agent info into roomConfig
let participantToken = await customTokenGenerationFunction(
options.roomName,
options.participantName,
options.agentName, // Available when using Session APIs with agentName
/* etc */
)
return TokenSourceResponse(serverURL: LIVEKIT_URL, participantToken: participantToken)
}
}
@main
struct SessionApp: App {
let session = Session(tokenSource: MyTokenSource())
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(session)
.alert(session.error?.localizedDescription ?? "Error", isPresented: .constant(session.error != nil)) {
Button(action: session.dismissError) { Text("OK") }
}
.alert(session.agent.error?.localizedDescription ?? "Error", isPresented: .constant(session.agent.error != nil)) {
AsyncButton(action: session.end) { Text("OK") }
}
}
}
}
struct ContentView: View {
@EnvironmentObject var session: Session
var body: some View {
if session.isConnected {
AsyncButton(action: session.end) {
Text("Disconnect")
}
Text(String(describing: session.agent.agentState))
} else {
AsyncButton(action: session.start) {
Text("Connect")
}
}
}
}
val LIVEKIT_URL = "<your LiveKit server URL>"
val tokenSource = remember {
TokenSource.fromCustom { options ->
// Run your custom token generation logic, using values in `options` as inputs
// `options` includes: roomName, participantName, agentName, roomConfig, etc.
// For agent applications, if calling a standard endpoint, package agent info into roomConfig
var participantToken = customTokenGenerationFunction(
options.roomName,
options.participantName,
options.agentName, // Available when using Session APIs with agentName
/* etc */
)
return@fromCustom Result.success(TokenSourceResponse(LIVEKIT_URL, participantToken))
}
}
val session = rememberSession(
tokenSource = tokenSource
)
Column {
SessionScope(session = session) { session ->
val coroutineScope = rememberCoroutineScope()
var shouldConnect by remember { mutableStateOf(false) }
LaunchedEffect(shouldConnect) {
if (shouldConnect) {
val result = session.start()
// Handle if the session fails to connect.
if (result.isFailure) {
Toast.makeText(context, "Error connecting to the session.", Toast.LENGTH_SHORT).show()
shouldConnect = false
}
} else {
session.end()
}
}
Button(onClick = { shouldConnect = !shouldConnect }) {
Text(
if (shouldConnect) {
"Disconnect"
} else {
"Connect"
}
)
}
// Agent provides state information about the agent participant.
val agent = rememberAgent()
Text(agent.agentState.name)
// SessionMessages handles all transcriptions and chat messages
val sessionMessages = rememberSessionMessages()
LazyColumn {
items(items = sessionMessages.messages) { message ->
Text(message.message)
}
}
val messageState = rememberTextFieldState()
TextField(state = messageState)
Button(onClick = {
coroutineScope.launch {
sessionMessages.send(messageState.text.toString())
messageState.clearText()
}
}) {
Text("Send")
}
}
}
import 'package:livekit_client/livekit_client.dart' as sdk;
final LIVEKIT_URL = "<your LiveKit server URL>";
final tokenSource = sdk.CustomTokenSource((options) async {
// Run your custom token generation logic, using values in `options` as inputs
// `options` includes: roomName, participantName, agentName, roomConfig, etc.
// For agent applications, if calling a standard endpoint, package agent info into roomConfig
final participantToken = await customTokenGenerationFunction(
options.roomName,
options.participantName,
options.agentName, // Available when using Session APIs with agentName
/* etc */
);
return TokenSourceResponse(serverUrl: LIVEKIT_URL, participantToken: participantToken);
});
final session = sdk.Session.fromConfigurableTokenSource(
tokenSource,
const TokenRequestOptions()
);
/* ... */
await session.start();
// Use session to further build out your application.
import { TokenSource } from 'livekit-client';
import { useSession, SessionProvider } from '@livekit/components-react';
const LIVEKIT_URL = "<your LiveKit server URL>";
// Create the TokenSource
//
// If your TokenSource.custom relies on other dependencies other than `options`, be
// sure to wrap it in a `useMemo` so that the reference stays stable.
const tokenSource = TokenSource.custom(async (options) => {
// Run your custom token generation logic, using values in `options` as inputs
// `options` includes: roomName, participantName, agentName, roomConfig, etc.
// For agent applications, if calling a standard endpoint, package agent info into roomConfig
const participantToken = await customTokenGenerationFunction(
options.roomName,
options.participantName,
options.agentName, // Available when using Session APIs with agentName
/* etc */
);
return { serverUrl: LIVEKIT_URL, participantToken };
});
export const MyPage = () => {
const session = useSession(tokenSource, { roomName: "room name to join" });
// Start the session when the component mounts, and end the session when the component unmounts
useEffect(() => {
session.start();
return () => {
session.end();
};
}, []);
return (
<SessionProvider session={session}>
{/* render the rest of your application here */}
</SessionProvider>
)
}

Manual token creation

If you need to create tokens programmatically on the backend (for example, to power a custom TokenSource or to use Room.connect directly), use the server SDK methods below.

Using Session APIs

If you're building a 1:1 agent application using Session APIs, token creation is handled automatically by your TokenSource. Use the examples below only if you're implementing your own token endpoint or using manual token generation.

lk token create \
--api-key <KEY> \
--api-secret <SECRET> \
--identity <NAME> \
--room <ROOM_NAME> \
--join \
--valid-for 1h
import { AccessToken, VideoGrant } from 'livekit-server-sdk';
import { RoomAgentDispatch, RoomConfiguration } from '@livekit/protocol';
const roomName = 'name-of-room';
const participantName = 'user-name';
const agentName = 'my-agent'; // Optional: for agent dispatch
const at = new AccessToken('api-key', 'secret-key', {
identity: participantName,
});
const videoGrant: VideoGrant = {
room: roomName,
roomJoin: true,
canPublish: true,
canSubscribe: true,
};
at.addGrant(videoGrant);
// Optional: Add agent dispatch for 1:1 agent applications
if (agentName) {
at.roomConfig = new RoomConfiguration({
agents: [
new RoomAgentDispatch({
agentName,
})
]
});
}
const token = await at.toJwt();
console.log('access token', token);
import (
"time"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/livekit"
)
func getJoinToken(apiKey, apiSecret, room, identity string, agentName string) (string, error) {
canPublish := true
canSubscribe := true
at := auth.NewAccessToken(apiKey, apiSecret)
grant := &auth.VideoGrant{
RoomJoin: true,
Room: room,
CanPublish: &canPublish,
CanSubscribe: &canSubscribe,
}
at.SetVideoGrant(grant).
SetIdentity(identity).
SetValidFor(time.Hour)
// Optional: Add agent dispatch for 1:1 agent applications
if agentName != "" {
roomConfig := &livekit.RoomConfiguration{
Agents: []*livekit.RoomAgentDispatch{{
AgentName: agentName,
}},
}
at.SetRoomConfig(roomConfig)
}
return at.ToJWT()
}
require 'livekit'
token = LiveKit::AccessToken.new(api_key: 'yourkey', api_secret: 'yoursecret')
token.identity = 'participant-identity'
token.name = 'participant-name'
token.video_grant=(LiveKit::VideoGrant.from_hash(roomJoin: true,
room: 'room-name'))
# Optional: Add agent dispatch for 1:1 agent applications
agent_name = 'my-agent'
if agent_name
token.room_config = LiveKit::Proto::RoomConfiguration.new(
agents: [
LiveKit::Proto::RoomAgentDispatch.new(
agent_name: agent_name
)
]
)
end
puts token.to_jwt
from livekit import api
import os
agent_name = "my-agent" # Optional: for agent dispatch
token = api.AccessToken(os.getenv('LIVEKIT_API_KEY'), os.getenv('LIVEKIT_API_SECRET')) \
.with_identity("identity") \
.with_name("name") \
.with_grants(api.VideoGrants(
room_join=True,
room="my-room",
))
# Optional: Add agent dispatch for 1:1 agent applications
if agent_name:
token = token.with_room_config(
api.RoomConfiguration(
agents=[
api.RoomAgentDispatch(
agent_name=agent_name
)
],
),
)
token = token.to_jwt()
use livekit_api::access_token;
use std::env;
fn create_token(agent_name: Option<String>) -> Result<String, access_token::AccessTokenError> {
let api_key = env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set");
let api_secret = env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set");
let mut token = access_token::AccessToken::with_api_key(&api_key, &api_secret)
.with_identity("identity")
.with_name("name")
.with_grants(access_token::VideoGrants {
room_join: true,
room: "my-room".to_string(),
..Default::default()
});
// Optional: Add agent dispatch for 1:1 agent applications
if let Some(agent_name) = agent_name {
token = token.with_room_config(livekit::RoomConfiguration {
agents: vec![livekit::AgentDispatch {
agent_name,
}],
});
}
token.to_jwt()
}

Agent dispatch in tokens

For 1:1 agent applications, you can include agent dispatch information in your tokens. This tells LiveKit which agent to automatically dispatch when a participant joins the room.

Using Session APIs

When using the Session APIs, you can provide the agent name at runtime. The Session API automatically packages agent information into room_config and includes it in token requests to your backend. Your token endpoint must accept room_config and pass it directly to the access token builder. This is the recommended approach for most applications.

For manual token generation: If you're generating tokens manually (not using Session APIs), you must include the agent dispatch information when you create the token. You cannot change the agent name at runtime with this approach unless you communicate it outside of LiveKit's abstractions.

For complete reference on token structure, grants, and permissions, see Tokens & grants.