i am setting egress and in case egress is failing ...
# ask-ai
s
i am setting egress and in case egress is failing i am deleting the room now the issue is in this case webhook_on_shutdown function is not running so no webhook to the backend, tell me when room is deleted why webhook_on_shutdown do not run and how to solve it
t
Thanks for reaching out, Ayush. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I understand you’re deleting the room when egress fails and you expect
webhook_on_shutdown
to run, but it’s not firing. I’ll explain why that happens and give practical ways to ensure your backend reliably gets notified when egress fails. Why
webhook_on_shutdown
doesn’t run when you delete the room •
webhook_on_shutdown
is a shutdown callback tied to the agent session (AgentSession / JobContext). It runs when the agent shuts down gracefully (e.g., via
session.shutdown()
/
ctx.shutdown()
). • Deleting a room via the server API forces participants (including agents) to be disconnected immediately. That forced disconnection does not run the agent’s graceful shutdown path, so any shutdown callbacks are skipped. • In short: room deletion is a room lifecycle event; shutdown hooks are part of the agent session lifecycle. Forcible room deletion bypasses agent shutdown hooks. What you can do instead (recommended) • Use LiveKit webhooks for reliable, backend-delivered notifications. LiveKit sends events like
egress_ended
(with
status: EGRESS_FAILED
) and
room_finished
even when the room is programmatically deleted. ◦ Configure your webhook URL and handle
egress_ended
in your backend. ◦ Webhooks are intended to notify your backend regardless of how the room ended, so you won’t rely on an agent to be able to run shutdown hooks. Two practical approaches you can pick from 1) Recommended — Backend reacts to webhooks • Let your backend receive and handle
egress_ended
(or
room_finished
) and perform whatever follow-up you need (notifications, logging, retries, cleanup). • This works even if the room is deleted forcefully and the agent never gets a chance to run shutdown callbacks. 2) If you must run agent shutdown logic first — perform a graceful teardown • Detect egress failure in the agent (or the service orchestrating the agent). • Call the agent’s graceful shutdown API (
session.shutdown()
/
ctx.shutdown()
or equivalent) so shutdown callbacks (including
webhook_on_shutdown
) run. • After the shutdown completes, have the backend delete the room. This ensures the agent hooks execute before the room is removed. Which approach you should use • If you need guaranteed backend notification regardless of how the room ends: use webhooks (approach 1). • If you have critical agent-side cleanup that must run before room deletion: implement a graceful shutdown flow where the agent shuts down first, then the room is deleted (approach 2). If helpful, I can: • Show an example webhook handler for
egress_ended
with the key fields to check. • Outline the minimal graceful shutdown sequence you’d call from the agent. • Help you decide which approach fits your architecture better — do you run agents in an environment where they can always perform a graceful shutdown before a room is deleted? Sources: Job lifecycle | LiveKit Documentation | examples/survey | livekit/livekit_composite | DeepWiki | server-sdk-ruby | livekit/livekit_composite | DeepWiki | Best practices for managing webhook event streams | LiveKit
s
give me code for graceful shutdown
t
Thanks — I’ll show you concise, runnable examples for performing a graceful agent shutdown so your
webhook_on_shutdown
callback runs before you delete the room. I’ll include Python and Node.js versions. Key points: • Call the agent shutdown method with drain enabled so pending audio completes. • Register your shutdown callback with the job context so it runs during shutdown. • Delete the room only after shutdown completes (either inside the shutdown callback or after awaiting the shutdown). Python (async) example
Copy code
import asyncio
from livekit_agents import AgentSession, JobContext, inference, silero
import logging

logger = logging.getLogger(__name__)

async def entrypoint(ctx: JobContext):
    # Create session with components
    session = AgentSession(
        stt=inference.STT(),
        llm=inference.LLM(),
        tts=inference.TTS(),
        vad=silero.VAD.load(),
    )

    # Register shutdown callback on the job context
    async def webhook_on_shutdown():
        <http://logger.info|logger.info>("Running post-shutdown webhook...")
        # perform webhook call to your backend, send logs, etc.
        # Example (pseudo):
        # await <http://http_client.post|http_client.post>(WEBHOOK_URL, json={"room": ctx.room, "reason": "shutdown"})
        <http://logger.info|logger.info>("Post-shutdown webhook complete.")

        # Optionally: delete the room from your server here (if permitted)
        # await delete_room_api(ctx.room)

    ctx.add_shutdown_callback(webhook_on_shutdown)

    # Start the session (agent implementation omitted)
    await session.start(agent=MyAgent(), room=ctx.room)

    # ... normal agent work ...

    # When you need to shut down gracefully:
    # This requests a graceful shutdown and allows the session to drain (finish pending speech)
    await session.shutdown(drain=True)

    # Wait for session to fully close if shutdown returns quickly
    await session.wait_closed()

    # If you prefer to delete room from here rather than inside webhook:
    # await delete_room_api(ctx.room)
Node.js / TypeScript example
Copy code
import { defineAgent, JobContext } from "livekit-agents";
import { inference } from "livekit-agents/inference";
import { silero } from "livekit-agents/vad";

export default defineAgent({
  entry: async (ctx: JobContext) => {
    const session = new voice.AgentSession({
      stt: new inference.STT(),
      llm: new inference.LLM(),
      tts: new inference.TTS(),
      vad: await silero.VAD.load(),
    });

    // Register shutdown callback
    ctx.addShutdownCallback(async () => {
      console.log("Running post-shutdown webhook...");
      // perform webhook call to your backend, send logs, etc.
      // await fetch(WEBHOOK_URL, { method: "POST", body: JSON.stringify({ room: ctx.room }) });
      console.log("Post-shutdown webhook complete.");

      // Optionally: delete the room from your server here
      // await deleteRoomApi(ctx.room);
    });

    await session.start({ agent: new MyAgent(), room: ctx.room });

    // ... normal agent work ...

    // Initiate graceful shutdown via the session API or job context
    // Using job context:
    await ctx.shutdown({ drain: true });

    // Or using the session API if provided:
    // await session.shutdown({ drain: true });

    // Wait until fully closed if an awaitable exists
    await session.waitClosed?.();
  },
});
Notes and best practices • Use
drain=True
(Python) or
drain: true
(Node) so pending TTS/playback finishes before teardown. • Register shutdown callbacks on the JobContext so they run as part of the graceful shutdown sequence. • If your orchestration sometimes forcefully deletes rooms, rely on backend webhooks (
egress_ended
,
room_finished
) as a fallback — but the graceful shutdown flow above ensures your agent-side
webhook_on_shutdown
runs when you trigger shutdown properly. • Place any room-deletion API call after shutdown completes (inside the shutdown callback or immediately after
await session.wait_closed()
), to ensure ordering. If you want, tell me: • which language (Python or Node) and environment you’re using and • whether you prefer deleting the room from the agent or from your backend, and I’ll tailor the exact code for your setup (including an HTTP webhook POST snippet). Sources: Job lifecycle | LiveKit Documentation