better-sandwich-65834
12/15/2025, 7:59 AMtry:
await ctx.api.sip.create_sip_participant(
api.CreateSIPParticipantRequest(
room_name=ctx.room.name,
sip_trunk_id=self.outbound_trunk_id,
sip_number=self.sip_number,
sip_call_to=phone_number,
participant_identity=phone_number,
wait_until_answered=True,
)
)
<http://logger.info|logger.info>("SIP participant created successfully")
except api.TwirpError as e:
logger.exception(
"Error creating SIP participant: %s, SIP status: %s %s",
e,
e.metadata.get("sip_status_code"),
e.metadata.get("sip_status"),
)
raisetall-airline-16756
12/15/2025, 7:59 AMtall-airline-16756
12/15/2025, 7:59 AMparticipant_identity (you show participant_identity=phone_number) or different identities?
1. Logs and errors
• Do you see any logs or exceptions when the first call drops or when creating the second call? In particular, any TwirpError metadata (sip_status_code, sip_status) or room/participant events.
• Please share the LiveKit server logs around the time of the incidents if available (timestamps are helpful).
1. Audio symptoms
• When you hear two voices, is one clearly the original caller and the other the bot/second caller, or do they sound like mixed streams from two call legs?
• Does the dual-voice issue occur only on the second call or sometimes on the first?
1. Configuration and expected behavior
• Which outbound trunk/provider are you using?
• Is the bot intended to handle multiple concurrent SIP legs in one room, or should only one SIP participant exist per phone number?
• Any telephony features in use (call transfer, forking, conferencing, early media, forking to multiple SIP endpoints)?
What I can do next once you provide that info:
• Look for patterns in the logs (e.g., second create request causing first participant to be removed, SIP trunk responses, or forking behavior).
• Suggest code changes or safeguards (for example, ensure only one active SIP participant per identity, or await explicit hangup before creating a new participant).
• Suggest debugging steps to capture SIP signaling and RTP behavior if needed.
Which of the requested logs/details can you share first?
Sources: Agents telephony integration | LiveKit docs | Make outbound calls | LiveKit docsbetter-sandwich-65834
12/15/2025, 8:01 AMtall-airline-16756
12/15/2025, 8:01 AMparticipant_identity and room for the second call while the first is still active. When two participants use the same identity, the server will disconnect the first one.
• Use a unique identity per call (e.g., append a timestamp/counter) or create a new room per call.
2) Wait for call end before creating the next
• Don’t create the second SIP participant until the first call’s sip.callStatus becomes hangup (or until the room is deleted). Queue outbound calls in your app logic to prevent signaling conflicts.
• You already use wait_until_answered=True — that helps for dialing, but it does not prevent starting a second call while the first is active.
3) Clean up the room and tracks
• After the call ends, explicitly delete the room to remove lingering participants/tracks:
await ctx.api.room.delete_room(room=ctx.room.name)
• Ensure any subscriptions to the first call’s audio tracks are unsubscribed/removed before starting the next call to avoid overlapping audio.
4) Check for overlapping audio sources
• Hearing two voices usually means audio from two active call legs is being mixed (either because the first participant wasn’t fully disconnected or because two agent instances are attached). Confirm only one SIP participant exists in the room when the second call is active.
5) Logs and TwirpError metadata to inspect
• When the first call drops or when creating the second, capture any exceptions and the TwirpError metadata fields sip_status_code and sip_status.
• Collect LiveKit agent logs (lk agent logs) around the event timestamps to see participant join/leave events and SIP signaling.
If you want, share:
• The code you use to set participant_identity and room_name for each outbound call.
• Relevant log snippets (TwirpError metadata, participant events, timestamps).
• Whether you expect to handle multiple concurrent calls to the same number or only sequential calls.
I can then point to exact code changes or a small pattern (queueing, identity generation, explicit room deletion) to fix the behavior. Would you like to share those logs or the identity/room logic next?
Sources: Rooms, participants, and tracks | LiveKit docs | Agents telephony integration | LiveKit docs | Make outbound calls | LiveKit docs | Making outbound calls | LiveKit docs | SIP APIs | LiveKit docsbetter-sandwich-65834
12/15/2025, 10:09 AMparticipant_identity and room for the second call while the first is still active. When two participants use the same identity, the server will disconnect the first one.
• Use a unique identity per call (e.g., append a timestamp/counter) or create a new room per call.
so even for same phone number i should set unique participant_identity?tall-airline-16756
12/15/2025, 10:09 AMparticipant_identity for each call, even when dialing the same phone number.
Why:
• The server treats each SIP call as a participant. If you reuse the same identity while the prior participant is still present, the server will disconnect the first participant, causing the drop/interrupt behavior you saw.
• Unique identities prevent collisions and avoid lingering audio/tracks from the previous call.
How to implement (simple options):
• Append a timestamp or counter: "+15105550123-169", "+15105550123-169-2".
• Or create a new room per call and keep identities unique within that room.
Also ensure you explicitly delete the room or confirm sip.callStatus == "hangup" before starting the next call to avoid overlapping audio.better-sandwich-65834
12/15/2025, 10:10 AMasync def create_sip_participant(self, ctx: JobContext, phone_number: str) -> None:
"""
Create SIP participant for outbound call.
Args:
ctx: Job context
phone_number: Phone number to call
Raises:
api.TwirpError: If SIP participant creation fails
"""
try:
await ctx.api.sip.create_sip_participant(
api.CreateSIPParticipantRequest(
room_name=ctx.room.name,
sip_trunk_id=self.outbound_trunk_id,
sip_number=self.sip_number,
sip_call_to=phone_number,
participant_identity=phone_number,
wait_until_answered=True,
)
)
<http://logger.info|logger.info>("SIP participant created successfully")
except api.TwirpError as e:
logger.exception(
"Error creating SIP participant: %s, SIP status: %s %s",
e,
e.metadata.get("sip_status_code"),
e.metadata.get("sip_status"),
)
raise
how i can confirm sip.callStatus == "hangup" before starting the next call to avoid overlapping audio.tall-airline-16756
12/15/2025, 10:11 AMsip.callStatus == "hangup") before creating the next SIP participant, and remind you to use unique participant identities.
1) High-level approach
• Listen for participant attribute changes (preferred) or poll the participant state.
• Wait until the SIP participant either leaves the room or its sip.callStatus becomes "hangup".
• Only then call create_sip_participant.
2) Example: wait using participant attribute events
import asyncio
async def wait_for_hangup(room, identity, timeout=30.0):
# quick lookup: if participant not present, assume ended
participant = room.participants_by_identity.get(identity)
if not participant:
return
# immediate check
if participant.attributes.get("sip.callStatus") == "hangup":
return
ev = asyncio.Event()
def on_attrs_changed(changed_attrs, p):
if p.identity != identity:
return
if "sip.callStatus" in changed_attrs and changed_attrs["sip.callStatus"] == "hangup":
ev.set()
room.on("participant_attributes_changed", on_attrs_changed)
try:
await asyncio.wait_for(ev.wait(), timeout)
except asyncio.TimeoutError:
# timeout handling: proceed or raise depending on desired behavior
pass
finally:
room.off("participant_attributes_changed", on_attrs_changed)
3) Integrate into your create method
async def create_sip_participant(self, ctx: JobContext, phone_number: str) -> None:
# ensure any existing call for this identity has finished
await wait_for_hangup(ctx.room, phone_number, timeout=30.0)
# use a unique identity per call to avoid replacing an active participant
identity = f"{phone_number}_{uuid4().hex[:8]}"
await ctx.api.sip.create_sip_participant(
api.CreateSIPParticipantRequest(
room_name=ctx.room.name,
sip_trunk_id=self.outbound_trunk_id,
sip_number=self.sip_number,
sip_call_to=phone_number,
participant_identity=identity,
wait_until_answered=True,
)
)
4) Alternatives and notes
• Polling: If events are unavailable/unreliable, poll room.participants_by_identity and participant.attributes["sip.callStatus"] every second until hangup or timeout.
• Room cleanup: After call end consider deleting the room (ctx.api.room.delete_room(room=ctx.room.name)) to ensure no lingering tracks.
• Identity: Yes — use a unique participant_identity per call (e.g., append timestamp/UUID). Reusing the same identity replaces/disconnects the existing participant and causes the behavior you observed.
• wait_until_answered=True waits for answer but does not wait for call end — you must monitor sip.callStatus for hangup.
If you want, share how your room/participant objects expose events in your runtime and I’ll adapt the snippet to that API.
Sources: Rooms, participants, and tracks | LiveKit docs | Agents telephony integration | LiveKit docs | SIP participant | LiveKit docs