early-butcher-66889
03/10/2026, 10:16 PMtall-airline-16756
03/10/2026, 10:16 PMtall-airline-16756
03/10/2026, 10:16 PMearly-butcher-66889
03/10/2026, 10:24 PMtall-airline-16756
03/10/2026, 10:25 PMpcm_s16le frames into a WAV file.
• Suggest the exact token/metadata fields to include for robust validation.
Which would you prefer?early-butcher-66889
03/10/2026, 10:37 PMtall-airline-16756
03/10/2026, 10:38 PMroom_finished (fired when the room becomes empty and is closed). That is the reliable signal to trigger your compile job.
What to listen for
• room_finished: Sent when the last participant leaves and the room is closed. Use the room name or SID in this webhook to correlate with the in‑progress recording for that room.
• Participant leave events are useful for progress/monitoring, but only room_finished guarantees the whole session is done.
Why agent hooks aren’t sufficient
• Agent-side hooks like an agent “end call” or session shutdown only reflect that agent’s connection. During warm transfers or other flows, the agent may disconnect while other participants remain in the room, so compiling at that point would be premature.
Recommended implementation flow
1. Start ingest and collect frames (via WebSocket egress).
2. Correlate the incoming frames to a room identifier (room.name or room.sid) and an egress job ID.
3. Subscribe to LiveKit webhooks on your secure endpoint and validate signatures.
4. On receiving `room_finished`:
◦ Verify the room ID matches your recording session.
◦ Trigger your compile/finalize job for all frames associated with that room.
◦ Mark the job idempotently (save event id) to avoid double-processing on retries.
1. Finalize storage/transcription and write audit logs.
Practical tips
• Verify webhook authenticity (validate signature/JWT) before acting.
• Respond quickly (2xx) to the webhook and run heavy processing asynchronously.
• Use idempotency (store processed webhook IDs) to handle retries safely.
• Include room metadata (room name/SID, egress job ID, timestamps) when you start egress so you can match the room_finished event precisely.
• If you need to be extra cautious, you can also wait a short configurable grace period after room_finished before compiling, to allow any in-flight frames to arrive.
If you’d like, I can:
• Provide a short webhook handler example that validates the event and kicks off an async compile job.
• Show how to correlate WebSocket egress connections to room IDs and persist in-progress state until room_finished.
Which would help you more — a code example for the webhook handler or a template for mapping incoming frames to room/egress IDs?
Sources: Server-side | livekit/livekit_composite | DeepWiki | Best practices for managing webhook event streams | LiveKit | server-sdk-js | livekit/livekit_composite | DeepWiki | Python SDKs | livekit/livekit_composite | DeepWiki | Job lifecycle | LiveKit Documentationearly-butcher-66889
03/10/2026, 10:39 PMtall-airline-16756
03/10/2026, 10:40 PMroom.name or room.metadata and forward/process events based on that value.
Other options to consider
• Agent-driven callbacks: Have the agent (or your orchestration service) call environment-specific endpoints directly when it’s appropriate. This is useful for agent-specific workflows but should not replace room lifecycle signals for finalizing recordings.
• Idempotency & retries: Webhooks are delivered at‑least‑once; use event IDs to dedupe and process asynchronously to avoid blocking LiveKit retries.
Recommended approach
• If you can, use separate LiveKit projects per environment. If that’s not possible, put environment/tenant IDs into the room metadata and handle routing in your centralized webhook service.
If you want, I can:
• Show how to structure room names/metadata for routing patterns, or
• Provide a minimal webhook handler example that routes events based on room metadata and demonstrates idempotent processing.
Which would be most helpful?
Sources: Best practices for managing webhook event streams | LiveKit | Server-side | livekit/livekit_composite | DeepWiki | examples/survey | livekit/livekit_composite | DeepWikiearly-butcher-66889
03/10/2026, 10:44 PMtall-airline-16756
03/10/2026, 10:45 PMegressId, roomName (or room SID), and an egress job ID in the WebSocket URL or headers (e.g., `wss://ingest.example.com/egress?egressId=xxx&room=myroom`).
◦ Persist an in‑progress record keyed by that egressId/room so you know which frames belong to which job.
1. Treat WebSocket open/close as lifecycle signals
◦ On `open`: mark egress as active.
◦ On `message`: append frames to your buffer/store and persist sequence/timestamp metadata.
◦ On `close`:
▪︎ If close is clean (e.g., code 1000) and the connection includes metadata indicating LiveKit finished sending, treat as end-of-stream and finalize (assemble frames → encode → store → transcribe).
▪︎ If close is abnormal (e.g., 1006) or network error, do NOT immediately finalize. Wait for a reconnection window (see next step).
1. Handle reconnections and partial delivery
◦ Implement a short grace/reconnect window (configurable, e.g., 20–60s) after abnormal close to allow LiveKit to reconnect and continue streaming.
◦ If LiveKit reconnects with the same egressId (or provides consistent metadata), resume appending frames to the same job.
◦ If no reconnection occurs within the window, treat the stream as ended and finalize.
1. Make finalization idempotent
◦ Ensure your compile job is idempotent (check if egressId is already processed) so multiple close events or retries don’t create duplicate artifacts.
1. Use robust logging and monitoring
◦ Log open/close codes, timestamps, bytes received, and egressId.
◦ Alert on repeated abnormal closes or incomplete files.
1. Optional stronger signals
◦ If you can, also watch higher-level room/participant state via your orchestration (participant list, token grants) to cross-check whether the room is likely finished.
◦ If you can call stopEgress (server API) when you know the room should end, that will reliably stop egress and produce the expected final state.
Caveats and limits
• WebSocket egress is a delivery channel—there’s no guaranteed at‑least‑once complete delivery of every frame. Plan for possible partial losses.
• LiveKit may retry connections; don’t treat the first close as definitive unless it’s a clean end and you validated metadata.
• If you can later enable egress_ended or room_finished webhook handling (single shared webhook with routing), that is still the most authoritative server-side signal that egress was completed.
Practical checklist to implement now
• Add `egressId`/`roomName` to WSS handshake.
• Persist in-progress state keyed by egressId.
• Finalize only on clean close or after a reconnect timeout.
• Make finalization idempotent.
• Add monitoring and an alert for abnormal close rates.
If you want, I can:
• Provide a minimal WebSocket receiver sketch that shows how to tag frames, persist state, and finalize idempotently.
• Suggest sensible default reconnect/window values and exact fields to pass in the handshake.
Which would you prefer — a code sketch for the receiver or recommended reconnect window values and state model?
Sources: Universal Egress | server-sdk-js | livekit/livekit_composite | DeepWiki | Best practices for managing webhook event streams | LiveKit | Universal Egress | server-sdk-ruby | livekit/livekit_composite | DeepWikiearly-butcher-66889
03/11/2026, 12:01 AMtall-airline-16756
03/11/2026, 12:01 AMtrack_published or participant_joined + check published tracks).
• When a participant publishes a track, call the server-side API to start a Track Egress for that track ID (start one egress job per track).
• Persist mapping: egressId -> trackId -> roomName (so you can find all files later).
Why: Track Egress records each participant’s track independently and gives you per-track artifacts you can merge later.
2) Maintain active participant state
• Subscribe to participant_joined and participant_left webhooks.
• Maintain an in-memory or DB record of currently active participants for each room (list or counter).
• Update that record atomically on each webhook (store webhook event id for dedupe).
Why: This gives you a reliable view of who’s still in the room independent of agent-side hooks.
3) Detect when the room is fully finished
• Rely on room_finished webhook — it fires when the last participant leaves and the room is closed.
• When you receive room_finished for a room:
◦ Confirm all expected egress jobs for that room have reached egress_ended (or poll/list egress jobs if needed).
◦ Only then trigger your compile/merge job.
Why: Agent end-call events are insufficient (warm transfers, etc.). room_finished is the authoritative server-side signal the session is done.
4) Use egress_ended to ensure recordings are ready
• egress_ended indicates an individual egress job has finalized and the file(s) are available.
• Before merging, either:
◦ Wait for egress_ended for each egressId you started, or
◦ After room_finished, check/list egress job statuses and wait for any in-progress egress to finish.
Why: Ensures you don’t merge partial files.
5) Make finalization idempotent and robust
• Deduplicate webhooks using their unique event id.
• Mark jobs processed by roomName + egressId so retries don’t re-run merges.
• Return 2xx quickly from webhook handlers and do heavy work asynchronously.
Operational tips and edge cases
• Late joiners: If a new participant joins after you started merging, your participant_joined logic should start a new track egress and you should postpone finalization until room_finished again.
• Tracking correlation: Put roomName, participantSid, and egressId into your DB record at egress start so you can map incoming egress_ended events to the right merge job.
• Partial delivery or reconnects (if using WebSocket egress): Don’t treat a temporary WebSocket close as definitive — use reconnection windows and only finalize when egress ends or the room_finished event arrives.
• Scale: If many participants publish/unpublish frequently, debounce start/stop operations and keep short-lived egress jobs where sensible (or start egress as soon as track is published to avoid missing early audio).
Short example flow
1. Participant A publishes track -> your service calls StartTrackEgress(trackId=A) and saves egressId_A.
2. Participant B (agent) publishes -> StartTrackEgress(trackId=B) -> save egressId_B.
3. During the call, your webhook consumer updates active participant list on join/leave.
4. Last participant leaves -> LiveKit sends room_finished.
5. On room_finished, your service:
◦ Confirms egress_ended for egressId_A and egressId_B (or waits for them).
◦ When all egress jobs are ended, runs your merge/compile job (FFmpeg or similar) to combine per-track files into the final artifact(s).
◦ Stores artifacts in your OIDC-compliant storage and logs audit metadata.
If you want, I can:
• Provide a minimal webhook handler sketch showing join/leave state tracking and dedupe logic, or
• Show an example of how to map egress job results to an FFmpeg merge command for per-track files.
Which help would you like next — a webhook state-tracking example or the post-processing merge example?
Sources: Universal Egress | Best practices for managing webhook event streams | LiveKit | Backup Egress Recordings | Why is there extra audio at the end of my recordings of an agent session? |...early-butcher-66889
03/12/2026, 4:27 PMtall-airline-16756
03/12/2026, 4:28 PM