steep-balloon-41261
02/21/2026, 9:08 AMtall-airline-16756
02/21/2026, 9:08 AMtall-airline-16756
02/21/2026, 9:09 AMroom_finished webhooks), and you want that delay removed or reduced.
Why this happens
• Rooms have a built-in departure_timeout that keeps the room open for a default 20 seconds after the last non-agent participant leaves. This protects against brief network blips or reconnects so the room isn't closed prematurely.
• Webhooks such as room_finished are only sent after the room is actually closed, so they follow the same timeout.
How you can remove or reduce the delay
• Set a smaller departure_timeout when creating the room via the Room Service API. For example, set departure_timeout to 5 or 1 second to shorten the wait before the room closes and the webhook fires.
◦ Example JSON for CreateRoom:
{
"name": "my-room",
"departure_timeout": 5
}
• If you need immediate closure, explicitly delete the room from your application when you decide the session is over. Calling the DeleteRoom API closes the room immediately and triggers the room_finished webhook without waiting for the timeout.
Which approach to use
• Use a reduced departure_timeout if you simply want faster automatic closure but still want some protection for short reconnects.
• Use immediate DeleteRoom when your app logic definitively determines the session is over and you require webhooks right away.
If you’d like, tell me:
• How you create rooms today (API call or server SDK), and
• Whether you prefer a short automatic timeout (eg. 1–5s) or programmatic immediate deletion.
I can then give the exact API/SDK snippet you should add to your implementation.
Sources: Ensuring Unique Room IDs with Dynamic Room Names | LiveKit | Why is there extra audio at the end of my recordings of an agent session? |...tall-airline-16756
02/21/2026, 9:19 AMdepartureTimeout: 1 but the room still waits ~20s before closing and firing room_finished. Let’s troubleshoot this so we can get webhooks firing faster.
Quick checklist — please confirm each item:
• How are you creating the room? (Are you calling the Room Service `CreateRoom`/`UpdateRoom` API server-side, or is the room created implicitly when the first participant joins?)
• Where did you set departureTimeout (server API request JSON, server SDK call, or client-side code)?
• Can you share the exact field name and value you send (or a sanitized request snippet)? Some SDKs/requests use departure_timeout while others may name it slightly differently.
• Are you calling any room-management endpoints later that might overwrite settings (e.g., an UpdateRoom that resets the timeout)?
• Do you have any server or proxy that might be enforcing a default timeout on the LiveKit server?
Common causes and next actions
• The departureTimeout must be set in the Room Service API (CreateRoom or UpdateRoom) on the server side. Setting it only client-side or in a join request that doesn’t create the room will not apply the value.
◦ If the room is created automatically on first join and you didn’t supply departure_timeout in that creation call, the default 20s remains.
• Some SDKs/requests require the field exactly as departure_timeout. Verify the exact parameter name and that the API returned success showing the room created/updated with that value.
• If you need immediate webhooks regardless of timeout, call DeleteRoom from your server when you detect the last participant has left. That closes the room immediately and triggers room_finished.
If you want, paste (sanitized) the server-side request you’re making to create/update the room and I’ll check it for the correct parameter name and placement and suggest the exact change. If you prefer, I can also provide the code snippet for setting departure_timeout in the API or show how to call DeleteRoom right after the last participant leaves.
Sources: Ensuring Unique Room IDs with Dynamic Room Names | LiveKittall-airline-16756
02/21/2026, 9:20 AMdepartureTimeout on the AccessToken’s roomConfig. That’s the important clue.
Why this is happening
• The departureTimeout that controls when the server auto-closes an empty room must be applied to the room on the server (via the Room Service CreateRoom or UpdateRoom API). Setting departureTimeout inside an AccessToken’s roomConfig does not change the server-side room setting, so the server still uses the 20s default and your room_finished webhook is delayed.
What you can do right now
• Apply departure_timeout when you create or update the room on the server:
◦ If you explicitly create rooms, include departure_timeout: 1 in the `CreateRoom`/`CreateRoomRequest`.
◦ If rooms are auto-created on first join, stop relying on implicit creation and instead create the room server-side with the desired departure_timeout before participants join.
• If you need immediate closure regardless of the server timeout, call DeleteRoom from your server when you detect the last participant has left; this forces immediate closure and triggers room_finished.
If you’d like, I can:
• Show the exact server-side API/SDK snippet to set departure_timeout (tell me which language or SDK you use), or
• Review a sanitized copy of the server-side room creation request you’re making to confirm the field name and placement.
Which would you prefer?tall-airline-16756
02/21/2026, 9:22 AMRoomConfiguration inside an access token controls what that token allows a client to request when creating a room (or what the client can do), but it does not change the server-side room settings after the room exists. To actually change the behavior of auto-closing, you must set departure_timeout on the room itself via the Room Service API (CreateRoom or UpdateRoom) from your server.
Below are two working server-side examples that create/update a room with a 1-second departure timeout and show how to immediately delete the room if you prefer absolute control. Pick the language you use; both examples use the server-side Room API so the setting takes effect.
Node.js (TypeScript / JavaScript) — using the official server API client
• Creates a room with departure_timeout: 1.
• Optionally deletes the room immediately when you decide.
// Example using the LiveKit server API (pseudo-code; adapt to your HTTP client or SDK)
// Replace LIVEKIT_BASE_URL, API_KEY, API_SECRET with your server credentials.
import fetch from "node-fetch";
const baseUrl = process.env.LIVEKIT_BASE_URL; // e.g. <https://your-livekit-host>
const apiKey = process.env.LIVEKIT_API_KEY;
const apiSecret = process.env.LIVEKIT_API_SECRET;
async function createRoom(roomName: string) {
const url = `${baseUrl}/v1/rooms`;
const body = {
name: roomName,
// Important: server-side field name is departure_timeout
departure_timeout: 1,
max_participants: 2,
// agents, metadata etc. can be added here if needed
};
const resp = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Basic ${Buffer.from(`${apiKey}:${apiSecret}`).toString("base64")}`,
},
body: JSON.stringify(body),
});
if (!resp.ok) {
const text = await resp.text();
throw new Error(`CreateRoom failed: ${resp.status} ${text}`);
}
return await resp.json();
}
async function deleteRoom(roomName: string) {
const url = `${baseUrl}/v1/rooms/${encodeURIComponent(roomName)}`;
const resp = await fetch(url, {
method: "DELETE",
headers: {
"Authorization": `Basic ${Buffer.from(`${apiKey}:${apiSecret}`).toString("base64")}`,
},
});
if (!resp.ok) {
const text = await resp.text();
throw new Error(`DeleteRoom failed: ${resp.status} ${text}`);
}
return;
}
// Usage
(async () => {
const roomName = "my-room";
await createRoom(roomName);
// When you detect last participant left, you can either wait for auto-close
// (now 1s) or call:
// await deleteRoom(roomName);
})();
Python — using direct HTTP to Room Service endpoints
import os
import requests
from requests.auth import HTTPBasicAuth
LIVEKIT_BASE_URL = os.getenv("LIVEKIT_BASE_URL") # <https://your-livekit-host>
API_KEY = os.getenv("LIVEKIT_API_KEY")
API_SECRET = os.getenv("LIVEKIT_API_SECRET")
def create_room(room_name: str):
url = f"{LIVEKIT_BASE_URL}/v1/rooms"
body = {
"name": room_name,
"departure_timeout": 1,
"max_participants": 2,
}
r = <http://requests.post|requests.post>(url, json=body, auth=HTTPBasicAuth(API_KEY, API_SECRET))
r.raise_for_status()
return r.json()
def delete_room(room_name: str):
url = f"{LIVEKIT_BASE_URL}/v1/rooms/{room_name}"
r = requests.delete(url, auth=HTTPBasicAuth(API_KEY, API_SECRET))
r.raise_for_status()
# Usage
if __name__ == "__main__":
room = create_room("my-room")
# Later, when last participant leaves:
# delete_room("my-room")
Key points to avoid confusion
• Setting departureTimeout in the client-side AccessToken’s roomConfig does not change the server’s room setting. The token’s config informs what the client is allowed to request when creating a room, but if the room already exists (or is auto-created without that server-side setting), the default 20s remains.
• The server-side field name is departure_timeout (snake_case) when using the Room Service API. Make sure you send it in the CreateRoom or UpdateRoom request.
• If a room is auto-created when the first participant joins (without your explicit CreateRoom call), the default 20s will be used. Create the room on your server before participants join to guarantee the custom timeout is applied.
• If you require webhooks instantly regardless of timeouts, call DeleteRoom when you detect the last participant left.
If you want, tell me:
• Which server language/SDK you prefer (Node, Python, Go, etc.), and I’ll provide a copy-paste-ready snippet tailored to your environment.
• Whether your current flow auto-creates rooms on first join or you can switch to creating rooms explicitly server-side before participants connect — I can suggest the minimal change to apply immediately.tall-airline-16756
02/21/2026, 9:35 AMcreateRoom. The root cause is that the Room Service expects the server-side field name departure_timeout (snake_case). In your snippet you used departureTimeout (camelCase), which is ignored, so the server fell back to the 20s default.
What to change
• Use departure_timeout: 1 when calling Room Service’s createRoom() (or updateRoom()).
• Ensure the room is actually created by that call (i.e., no other code auto-creates the room without the timeout).
• If you need absolute immediate webhook delivery, call deleteRoom() when the last participant leaves.
Fully working Node.js example (server-side)
• This example uses the server RoomServiceClient to create a room with a 1s departure timeout and shows how to delete the room. Replace env values with your credentials.
import { RoomServiceClient, RoomAgentDispatch } from "livekit-server-sdk";
import { AccessToken } from "livekit-server-sdk";
const LIVEKIT_URL = process.env.LIVEKIT_URL; // e.g. <https://your-livekit-host>
const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY;
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET;
async function createRoomAndToken(roomName: string, agentName: string, metadata: any) {
const roomService = new RoomServiceClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
// NOTE: Use snake_case `departure_timeout` here
await roomService.createRoom({
name: roomName,
departure_timeout: 1, // <- correct field name
max_participants: 2,
agents: [
new RoomAgentDispatch({
agentName: agentName,
metadata: JSON.stringify(metadata),
}),
],
});
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, {
identity: `profile-123`,
ttl: "1h",
});
at.addGrant({
room: roomName,
roomJoin: true,
canPublish: true,
canSubscribe: true,
});
const token = await at.toJwt();
return { token, wsUrl: LIVEKIT_URL };
}
async function deleteRoom(roomName: string) {
const roomService = new RoomServiceClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
await roomService.deleteRoom(roomName);
}
// Usage example:
// await createRoomAndToken("my-room", "agentA", { foo: "bar" });
// When last participant leaves, either rely on departure_timeout (1s) OR call deleteRoom("my-room")
Quick verification steps if you still see 20s
• Confirm the createRoom call returns success and the response includes the room details.
• Confirm no other code path (e.g., implicit auto-creation on first join or another service) creates the room without the departure_timeout set.
• If using proxies or middleware, ensure they’re not transforming request body keys.
If you want, paste the exact server-side createRoom() call (sanitized) you’re using now and I’ll confirm it’s using departure_timeout correctly and that the create response is OK. If you prefer another language, I can provide the same working example in that SDK.
Sources: server-sdk-go | livekit/livekit_composite | DeepWiki | Python SDKs | livekit/livekit_composite | DeepWiki | Ensuring Unique Room IDs with Dynamic Room Names | LiveKit | server-sdk-ruby | livekit/livekit_composite | DeepWikitall-airline-16756
02/21/2026, 9:37 AMdeparture_timeout. That server field is what actually controls the room auto-close behavior.
• The Node.js server SDK’s RoomServiceClient maps your JS call into that server API. Even in Node, the server-side field name must be set when creating/updating the room so the server applies the timeout. If the SDK method you call accepts camelCase, it must still result in the server sending departure_timeout. If it doesn’t, the server will use the 20s default.
Working Node.js snippet you can copy/paste
• This example uses the official livekit-server-sdk for Node and shows a createRoom call that ensures the room is created server-side with a 1s departure timeout, then issues an AccessToken for clients. Replace env values with your credentials.
import { RoomServiceClient, RoomAgentDispatch, AccessToken } from "livekit-server-sdk";
const LIVEKIT_URL = process.env.LIVEKIT_URL; // e.g. <https://your-livekit-host>
const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY;
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET;
export async function createRoomAndToken(roomName: string, agentName: string, metadata: any) {
const roomService = new RoomServiceClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
// Ensure the server receives the departure timeout.
// Depending on your SDK version, either of these approaches will work:
// 1) If the SDK accepts snake_case directly, pass `departure_timeout`.
// 2) If the SDK uses camelCase but maps correctly, pass `departureTimeout`.
// The key is to confirm the server actually stored the value (see verification step below).
await roomService.createRoom({
name: roomName,
// Try snake_case first — this is what the server expects
departure_timeout: 1,
// If your SDK version requires camelCase, use the line below instead:
// departureTimeout: 1,
max_participants: 2,
agents: [
new RoomAgentDispatch({
agentName: agentName,
metadata: JSON.stringify(metadata),
}),
],
});
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, {
identity: `profile-${Math.random().toString(36).slice(2, 8)}`,
ttl: "1h",
});
at.addGrant({
room: roomName,
roomJoin: true,
canPublish: true,
canSubscribe: true,
});
const token = await at.toJwt();
return { token, wsUrl: LIVEKIT_URL };
}
Quick verification steps (please run these so we can be sure the server applied the timeout)
1. After createRoom returns, fetch the room from the RoomService to read back settings:
◦ roomService.getRoom({ room: roomName }) or roomService.listRooms() depending on SDK.
1. Confirm the returned room object shows departure_timeout (or the numeric timeout field) equals 1.
◦ If it shows 20, the server didn’t get the value and we need to adjust how we pass it.
1. If the room shows the correct timeout but you still see 20s before webhook, try explicitly deleting the room to verify webhooks fire immediately:
◦ await roomService.deleteRoom(roomName);
◦ If delete triggers room_finished immediately, webhook delivery is working and the remaining delay was due to auto-close timeout still being set to 20.
If verification fails, here’s how I’ll help next
• Paste the exact createRoom response (sanitized) or the SDK version you’re using and I’ll tell you whether to use departure_timeout or departureTimeout for your SDK version.
• If you prefer, I’ll give a tiny test script you can run that creates the room, reads it back, and prints the timeout value so we can see what the server actually stored.
One last note
• I understand the urgency — if you need absolutely immediate webhooks, call deleteRoom(roomName) when you detect the last participant left. That bypasses any timeout and fires room_finished right away.
If you want, send me:
• Your livekit-server-sdk version (from package.json) and the createRoom response, and I’ll provide the exact one-line change you need.tall-airline-16756
02/21/2026, 9:38 AMdepartureTimeout. The SDK will map that to the server-side field. Let’s keep this short and practical so you can verify and fix it immediately.
What to do (one clear action)
• Use departureTimeout: 1 in your server-side createRoom() call (this matches the TypeScript types).
• After creating the room, read it back and log the timeout to confirm the server stored the value.
• If you need immediate webhooks regardless of timeout, call deleteRoom() when the last participant leaves.
Minimal copy-paste-ready Node/TypeScript example
• Replace env values with your credentials. This example uses the typed SDK calls most commonly used; if your SDK version differs, tell me the version and I’ll tailor it.
import { RoomServiceClient, RoomAgentDispatch, AccessToken } from "livekit-server-sdk";
const LIVEKIT_URL = process.env.LIVEKIT_URL!;
const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY!;
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET!;
export async function createRoomAndToken(roomName: string, agentName: string, metadata: any) {
const roomService = new RoomServiceClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
// Use camelCase in Node/TypeScript SDK
await roomService.createRoom({
name: roomName,
departureTimeout: 1, // <- correct for the Node/TypeScript SDK
maxParticipants: 2,
agents: [
new RoomAgentDispatch({
agentName,
metadata: JSON.stringify(metadata),
}),
],
});
// Read back the room to verify the timeout was applied.
// Common typed forms: getRoom({ room: roomName }) or getRoom(roomName)
// Use the one that matches your SDK version. If unsure, try the first.
let room: any;
try {
room = await (roomService as any).getRoom({ room: roomName });
} catch {
room = await (roomService as any).getRoom(roomName);
}
// Inspect both possible fields and print a clear message
const applied = room?.departureTimeout ?? room?.departure_timeout;
if (applied != null) {
console.log(`Configured departure timeout: ${applied} seconds`);
} else {
console.log("No departure timeout found in room object — the server did not apply it.");
}
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, {
identity: `profile-${Math.random().toString(36).slice(2, 8)}`,
ttl: "1h",
});
at.addGrant({
room: roomName,
roomJoin: true,
canPublish: true,
canSubscribe: true,
});
const token = await at.toJwt();
return { token, wsUrl: LIVEKIT_URL };
}
export async function forceDeleteRoom(roomName: string) {
const roomService = new RoomServiceClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
// Common typed forms: deleteRoom({ room: roomName }) or deleteRoom(roomName)
try {
await (roomService as any).deleteRoom({ room: roomName });
} catch {
await (roomService as any).deleteRoom(roomName);
}
}
Quick verification steps
1. Call createRoomAndToken(...) before any participant joins.
2. Check console: it should print "Configured departure timeout: 1 seconds".
◦ If it prints 1, the server applied it and auto-close should occur ~1s after last non-agent participant leaves.
◦ If it prints the 20s default or “No departure timeout found”, the value wasn’t applied.
1. If the value isn’t applied:
◦ Ensure no other code path auto-creates the room (create the room server-side before first join).
◦ Ensure your createRoom call is the one that actually creates the room (check the create response).
1. To guarantee immediate webhook firing while we troubleshoot, call forceDeleteRoom(roomName) when you detect the last participant left.
If you still see 20s after these steps, send:
• The exact installed livekit-server-sdk version from your package.json, and
• The console output of the room object (sanitized).
I’ll check the correct typed signatures for your SDK version and give the single exact change you need. I’m here to help get this working quickly.tall-airline-16756
02/21/2026, 9:40 AMdepartureTimeout: 1 is correctly applied, so that setting is working. The reason you still see the ~20s (actually you observed ~20s, but your room shows emptyTimeout: 300) delay is due to agents and how LiveKit treats them.
What’s happening
• The departureTimeout only controls how long the room stays open after the last non-agent participant leaves.
• If there are any agents present (or configured as agent dispatches), the room is treated as not “empty” for a different timeout path. The server keeps the room open according to the room’s emptyTimeout (in your object it’s 300s), and that can result in later webhook timing behavior.
• Your room object shows:
◦ departureTimeout: 1 — applied
◦ emptyTimeout: 300 — this controls how long the room remains when agents (or other server-side entities) are considered present or when the room is considered “empty” by a different rule set
How to fix it (pick one)
• If you want the room to close quickly even when agents are involved:
◦ Set a lower emptyTimeout when you create the room (e.g., 1 or 5 seconds). That will cause the room to close faster when agent-related conditions would otherwise keep it open.
• If agents are not needed to keep the room open, remove agents from the room configuration so the departureTimeout behavior alone controls closure.
• For absolute immediate control, call deleteRoom(roomName) on the server the moment you detect the session is over. That forces immediate closure and triggers room_finished right away.
Suggested change (Node/TypeScript)
• Include emptyTimeout (camelCase for Node SDK) in your createRoom() call along with departureTimeout. Example:
await roomService.createRoom({
name: roomName,
departureTimeout: 1, // keeps automatic close quick after last non-agent leaves
emptyTimeout: 1, // ensures room closes quickly even with agents/empty-state rules
maxParticipants: 2,
agents: [
new RoomAgentDispatch({
agentName,
metadata: JSON.stringify(metadata),
}),
],
});
Verification steps
1. After createRoom returns, call getRoom and confirm:
◦ departureTimeout === 1
◦ emptyTimeout === 1
1. Reproduce the flow: have the non-agent participant leave and watch webhook timing. If you still see delay, immediately call deleteRoom(roomName) when you detect last participant left to verify webhooks fire instantly.
2. If deleteRoom triggers room_finished instantly but auto-close does not, the timeout path is still governed by emptyTimeout (so the adjust above is required).
Why deleteRoom is useful for testing
• Calling deleteRoom forces immediate closure; if that fires room_finished without delay, it proves webhook delivery is functioning and the remaining delay is a server timeout configuration.
If you prefer, I can:
• Provide a full minimal test script that creates a room with departureTimeout: 1 and emptyTimeout: 1, then simulates a participant join/leave and prints webhook timing; or
• Give the exact place to add a deleteRoom() call in your current flow so you get immediate webhook delivery while we finalize timeout values.
Tell me which you want and I’ll give the exact code.tall-airline-16756
02/21/2026, 9:47 AM// call this when you detect the human left
await roomService.deleteRoom({ room: roomName }); // or deleteRoom(roomName) depending on SDK
• Use this if you want absolute control and no delays.
2) Make the agent leave immediately after the human leaves
• Modify the agent to exit the room as soon as the human disconnects (or after a very short grace period). Once the agent leaves, the room will finish according to emptyTimeout (which you set to 1s).
• If your agent runs on your servers, have it call the same delete or disconnect routine when it sees the participant_left event.
Why this works
• departureTimeout controls auto-close after the last non-agent leaves, but if an agent remains, the room is still considered non-empty and governed by emptyTimeout or the agent’s lifecycle.
• Deleting the room or ensuring agents leave removes that final participant, letting room_finished fire immediately (or after the 1s emptyTimeout you set).
Short checklist to verify behavior
• Confirm your webhook log order: participant_left (human) → participant_left (agent) → room_finished. If agent_left is delayed, the room will be delayed.
• Try this quick test:
a. Create room with departureTimeout:1 and emptyTimeout:1.
b. Connect a human + agent.
c. Have human leave and immediately call roomService.deleteRoom(roomName).
d. Check webhook: room_finished should appear immediately after deleteRoom call.
If you want, I can:
• Paste a tiny runnable test script that creates a room, simulates join/leave, and calls deleteRoom (Node/TypeScript) so you can reproduce quickly.
• Help you add the deleteRoom call into the exact place in your code where you detect the human participant left — paste that snippet and I’ll show the one-line change.
Tell me which you prefer and I’ll give the exact code to drop in.