white-telephone-81843
12/18/2025, 11:23 AMtall-airline-16756
12/18/2025, 11:23 AMtall-airline-16756
12/18/2025, 11:24 AMclose event — it's emitted when the session ends (user disconnected, room deleted, or error).
Example (Python-style):
@session.on("close")
async def on_close(event):
if event.error:
logger.error(f"Session ended with error: {event.error}")
else:
<http://logger.info|logger.info>("User hung up or session ended gracefully")
You can also register a shutdown callback to run cleanup when the session is ending:
async def on_shutdown():
<http://logger.info|logger.info>("Agent session is shutting down (user may have hung up)")
ctx.add_shutdown_callback(on_shutdown)
If you need per-participant disconnection (in multi-participant rooms), monitor participant disconnection events on the Room object instead. Would you like a language-specific example (Node/Go/Python) or guidance for multi-participant handling?
Sources: Events and error handling | LiveKit docs | How to detect and handle silence during callswhite-telephone-81843
12/18/2025, 11:26 AMtall-airline-16756
12/18/2025, 11:27 AMsip.callStatus on the SIP participant:
◦ sip.callStatus == "hangup" → caller hung up.
◦ Other statuses like active, automation, dialing indicate call still active or in transfer flow.
• Track when your agent initiates a DTMF transfer:
◦ Set a short-lived flag (e.g., is_transferring = True) before sending DTMF, clear it after transfer completes or after a timeout.
◦ When a participant disconnects, if is_transferring is true treat it as a transfer; otherwise consult sip.callStatus.
• Monitor events:
◦ Listen for participant_disconnected or the AgentSession close event to detect the leave.
◦ Also subscribe to participant_attributes_changed and watch sip.callStatus changes for confirmation.
Example pattern (Python-style):
self.is_transferring = False
async def transfer_with_dtmf(...):
self.is_transferring = True
await self.session.room.local_participant.publish_dtmf(digits)
# optionally wait/validate transfer completion
await asyncio.sleep(1)
self.is_transferring = False
@room.on("participant_disconnected")
def on_participant_disconnected(participant):
call_status = participant.attributes.get("sip.callStatus")
if self.is_transferring:
<http://logger.info|logger.info>("Participant left: treated as DTMF transfer")
elif call_status == "hangup":
<http://logger.info|logger.info>("Participant hung up")
else:
<http://logger.info|logger.info>(f"Participant left, status: {call_status}")
Would you like a Node or Go code example, or help deciding where to place the transfer flag in your current agent flow?
Sources: SIP participant | LiveKit docs | SIP Lifecycle Management Agent | LiveKit docs | Handling DTMF | LiveKit docs