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.
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 TokenSourceconst 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 roomConfigconst 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 roomconst 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 roomConfigconst 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 unmountsuseEffect(() => {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 LiveKitComponentslet 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 roomConfiglet participantToken = await customTokenGenerationFunction(options.roomName,options.participantName,options.agentName, // Available when using Session APIs with agentName/* etc */)return TokenSourceResponse(serverURL: LIVEKIT_URL, participantToken: participantToken)}}@mainstruct 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: Sessionvar 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 roomConfigvar 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 messagesval 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 roomConfigfinal 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 roomConfigconst 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 unmountsuseEffect(() => {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.
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 dispatchconst 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 applicationsif (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 := truecanSubscribe := trueat := 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 applicationsif 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 applicationsagent_name = 'my-agent'if agent_nametoken.room_config = LiveKit::Proto::RoomConfiguration.new(agents: [LiveKit::Proto::RoomAgentDispatch.new(agent_name: agent_name)])endputs token.to_jwt
from livekit import apiimport osagent_name = "my-agent" # Optional: for agent dispatchtoken = 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 applicationsif 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 applicationsif 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.
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.