Hello, When I make outbound call from my agent an...
# ask-ai
a
Hello, When I make outbound call from my agent and when customer answer call my asterisk server sending me 200 OK. But after this my agent does not start speaking. With another provider same flow is working. Before I was initiating another call to my agent when customer answers my call. But thsi cause 2 rooms generation for same call and I do not want to create 2 channels for 1 call. How I can handle 200 OK event to start talking my agent?
t
Thanks for reaching out, Nikunj. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
a
Ok
t
Thanks — I understand you get a SIP
200 OK
from Asterisk when the customer answers but your agent never starts speaking, and you want to avoid creating a second room/channel. Short answer: have the agent wait for the SIP participant to join the same LiveKit room (or wait until the call is answered) and then trigger the agent to speak — do not create a second call. Two ways to achieve this: 1) Use
wait_until_answered=True
when creating the SIP participant so your agent flow pauses until Asterisk returns 200 OK, then continue your agent logic in the same room. • This ensures the SIP participant joins the existing room before you start playback or TTS. 2) Listen for the
participant_connected
(or equivalent SIP participant join) event and start the agent’s audio only after you detect the SIP participant in the room. • Filter for SIP participants (
participant.kind == SIP
) or check
sip
attributes, then call your speech/TTS/send-audio routine. Recommended flow to avoid double rooms: 1. Dispatch agent into room A. 2. Agent calls
create_sip_participant
for the customer with
wait_until_answered=True
(or create participant and wait for
participant_connected
). 3. When you receive the participant-connected event (or your
wait_until_answered
returns), start the agent speaking in room A. If you share which agent SDK/language you’re using (Python, JS, etc.) and a short snippet of how you currently create the SIP participant, I can give a concrete code example to plug into your flow. Sources: Agents telephony integration | LiveKit docs | SIP Lifecycle Management Agent | LiveKit docs
Thanks — glad we're aligned. Can you confirm which agent language/runtime you're using (Python, Node, or other)? The cleanest fix uses the
wait_until_answered=True
flow and triggering speech on the room
participant_connected
event — that option is available for Python agents. If you're on Python I can share the exact code snippets to add; if you use Node or another runtime I’ll suggest the closest alternative.
a
I am using python
t
Thanks — since you’re using Python, here’s a concise, practical setup you can add so your agent starts speaking after the SIP participant answers without creating a second room. 1) Create the SIP participant with wait_until_answered=True (this blocks until 200 OK):
Copy code
await ctx.api.sip.create_sip_participant(
    api.CreateSIPParticipantRequest(
        room_name=ctx.room.name,
        sip_trunk_id="ST_xxxx",      # your trunk ID
        sip_call_to="+15105550123",  # destination number
        participant_identity="caller",
        wait_until_answered=True,
    )
)
2) Listen for the room participant join and trigger the agent reply when the SIP participant connects:
Copy code
@ctx.room.on("participant_connected")
async def on_participant_connected(participant: rtc.RemoteParticipant):
    # detect SIP participant
    if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
        # optional: check identity or metadata if you need to filter specific calls
        await session.generate_reply(
            instructions="Hello, thank you for answering. How can I assist you today?"
        )
Notes: • Keep the agent connected to the same room before creating the SIP participant so the SIP leg joins that room on answer. • Avoid creating another call/room — using
wait_until_answered=True
plus
participant_connected
ensures a single room and that speech starts only after the customer answers. If you’d like, I can provide a complete minimal agent file showing connection, dispatch, and these handlers.
a
My call flow is like below: Agent originate calls to asterisk. Asterisk dial customer number using carrier and customer answer call. For asterisk it is 2nd channel Once customer answered call, asterisk just sending 200 OK to livekit. Will this flow will work with your given python solution?
t
Thanks — thanks for the clear call flow. Yes, your flow will work with the Python solution I gave, as long as two conditions are met: • The agent is already connected to the same LiveKit room before you create the SIP participant. • When Asterisk sends the 200 OK back to LiveKit, that SIP leg is allowed to join the same room and LiveKit emits the
participant_connected
event. How it maps to your steps: • Agent originates to Asterisk (agent stays in room). • Asterisk dials the customer via carrier and, when the customer answers, sends 200 OK to LiveKit. LiveKit treats that as the SIP participant joining the room. • Using
wait_until_answered=True
will block until that 200 OK arrives (so your code waits for answer) and/or you can rely on the
participant_connected
event to trigger the agent speech once the SIP participant joins. Quick checks to ensure success: • Verify the agent connects to the room before calling
create_sip_participant
. • Confirm Asterisk’s 200 OK reaches LiveKit and that the SIP participant is created with the expected kind/identity (you can log participant metadata). • Use
participant_connected
to start speech (or follow up right after the
create_sip_participant
call if you use
wait_until_answered=True
). If you want, I can share a minimal full agent example that includes room connect,
create_sip_participant(..., wait_until_answered=True)
, and the
participant_connected
handler so you can drop it into your setup. Which would you prefer — full example or just the handler + call snippet?
a
Yes, Please share full example.
t
Thanks — below is a complete, minimal Python agent you can run as-is (adjust env values). It dials out via Asterisk/SIP with
wait_until_answered=True
, waits for the 200 OK / SIP participant to join the same room, then speaks. This avoids creating a second room or extra calls. Save as outbound_agent.py and update your .env.
Copy code
import asyncio
import json
import logging
import os
from dotenv import load_dotenv

from livekit import api, rtc
from livekit.agents import AgentServer, JobContext, VoiceAssistantConfig, llm, stt, tts
from livekit.agents.voice_assistant import VoiceAssistant
from livekit.plugins import silero

load_dotenv()
logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
logger = logging.getLogger("outbound-agent")

server = AgentServer()

def prewarm(proc):
    proc.userdata["vad"] = silero.VAD.load()

server.setup_fnc = prewarm

class OutboundAgent(VoiceAssistant):
    def __init__(self, ctx: JobContext):
        super().__init__(
            job_context=ctx,
            assistant_name="Outbound Caller",
            instructions="You are an outbound caller. Greet the user when they answer.",
            config=VoiceAssistantConfig(
                pre_processor=llm.PromptFnc(llm.prompt_default),
                model=llm.LLM(model="gpt-4o-mini"),
                stt=stt.DeepgramSTT(model="nova-3"),
                tts=tts.CartesiaTTSElement(voice="british english male 1"),
                vad=ctx.proc.userdata["vad"],
            ),
        )

@server.rtc_session()
async def entrypoint(ctx: JobContext):
    <http://logger.info|logger.info>(f"Agent started for room {ctx.room.name}")

    # read phone number from job metadata
    metadata = json.loads(ctx.job.metadata or "{}")
    phone_number = metadata.get("phone_number")
    if not phone_number:
        logger.error("phone_number missing in job metadata")
        return

    sip_trunk_id = os.getenv("SIP_OUTBOUND_TRUNK_ID")
    if not sip_trunk_id:
        logger.error("SIP_OUTBOUND_TRUNK_ID not set")
        return

    # Ensure agent is connected to the room (optional: keep connected before dialing)
    await ctx.connect(auto_subscribe=api.AutoSubscribe.AUDIO_ONLY)

    <http://logger.info|logger.info>(f"Dialing {phone_number} via trunk {sip_trunk_id}")
    try:
        # This call blocks until 200 OK is received (call answered)
        await ctx.api.sip.create_sip_participant(
            api.CreateSIPParticipantRequest(
                room_name=ctx.room.name,
                sip_trunk_id=sip_trunk_id,
                sip_call_to=phone_number,
                participant_identity=f"called-{phone_number}",
                wait_until_answered=True,
                play_dialtone=True,
            )
        )
    except Exception as e:
        logger.error(f"Failed to create SIP participant: {e}")
        return

    <http://logger.info|logger.info>("Call answered — starting assistant speech")

    assistant = OutboundAgent(ctx)
    # TTS greeting will be played into the room (and therefore to the SIP leg)
    await assistant.say("Hello, thank you for answering. How can I help you today?")

    # Keep session alive until disconnected
    await ctx.wait_for_disconnected()
    <http://logger.info|logger.info>("Session ended")

if __name__ == "__main__":
    server.run()
.env example:
Copy code
LIVEKIT_URL=<wss://your-instance.livekit.cloud>
LIVEKIT_API_KEY=your-api-key
LIVEKIT_API_SECRET=your-api-secret
SIP_OUTBOUND_TRUNK_ID=ST_XXXXXXXXXXX
Notes and checklist for your Asterisk flow: • Agent must be in the same LiveKit room before/during the outbound SIP create call so the SIP leg joins that room on answer. • Asterisk must forward the 200 OK to LiveKit so the SIP participant is created/joined;
wait_until_answered=True
makes the agent code block until that 200 OK arrives. • Use the
participant_connected
event or the post-
create_sip_participant
continuation (as shown) to start speaking — both ensure speech only starts after answer. • No second room is created with this flow. If you want, I can: • Provide a version that logs participant attributes when the SIP leg joins, or • Add a
participant_connected
handler instead of relying on the blocking call. Which would you prefer? Sources: Outbound Calling Script | LiveKit docs | SIP Lifecycle Management Agent | LiveKit docs | Agents telephony integration | LiveKit docs
a
Should asterisk send any RTP along with 200 response?
t
Good question — thanks for asking. The SIP
200 OK
itself carries the SDP answer (the session description), not RTP media. RTP media normally starts after the 200 OK is exchanged and the ACK is completed. So Asterisk does not send RTP "inside" the 200 OK; it sends the SDP in the 200 OK and then begins sending RTP packets once the SIP handshake (200 OK → ACK) is complete and media is established. For your flow with LiveKit + the Python agent: • LiveKit needs the SIP 200 OK with SDP so the SIP participant can join the room. • RTP media from Asterisk to LiveKit should begin after the 200 OK/ACK handshake. If you see no audio after answer, check that Asterisk actually starts sending RTP to LiveKit after the ACK and that LiveKit receives it. Would you like help verifying the SDP/RTP exchange (examples of the 200 OK SDP to look for or packet-level checks to run)?
a
Ok currently I do not see ACK from livekit after asterisk's 200 OK.
t
Thanks — I hear you: Asterisk sends 200 OK but LiveKit is not ACKing. Let’s diagnose that so media can start. What I’ll cover: the expected SIP flow, likely reasons LiveKit wouldn't send an ACK, quick checks you can run, and the logs/packets I need if you want me to dig deeper. Expected SIP flow • Caller (LiveKit) sends INVITE → Asterisk replies 200 OK with SDP → LiveKit must send ACK (completing handshake) → RTP flows. • If LiveKit never ACKs, RTP will not start and the call remains unestablished from LiveKit’s side. Common causes and checks 1. 200 OK SDP incompatible or malformed ◦ Check the 200 OK SDP for required codecs and correct connection/c= and m= lines. Missing a common audio codec (e.g., PCMU/opus) can cause LiveKit to reject. ◦ Ensure Asterisk’s SDP IP/port are reachable from LiveKit (no private IPs behind NAT). 1. Transport or signaling mismatch ◦ Confirm whether SIP is over UDP/TCP/TLS and that LiveKit expects that transport for this trunk. ◦ If Asterisk sends 200 OK on a different source IP/port than the INVITE transaction, the ACK may be misrouted. 1. Transaction/state issues (branch/Call-ID/CSeq) ◦ Verify Call-ID, CSeq, and Via branch values in the 200 OK match the INVITE transaction LiveKit sent. Mismatched headers can cause LiveKit to ignore the 200 OK. 1. Authentication / Proxy behavior ◦ If your trunk uses a proxy or requires authentication/record-route behavior, confirm Asterisk isn’t introducing extra Record-Route headers that change dialog handling. 1. NAT / RTP pinholing / symmetric RTP ◦ Even if ACKs are exchanged, asymmetric NAT can prevent RTP. But first we must confirm ACK is sent. Quick troubleshooting steps (do these first) 1. Capture SIP trace on Asterisk side: ◦ Enable sip (chan_sip) or pjsip debug and capture the INVITE → 200 OK → (missing) ACK. Save full SIP logs. 1. Capture packets on the LiveKit-facing interface (tcpdump) ◦ Example: tcpdump -w sip_trace.pcap port 5060 or capture SIP/TLS port and RTP ports. This proves whether LiveKit sent ACK or not and shows packet routing. 1. Inspect the 200 OK message: ◦ Paste the 200 OK SIP headers + SDP (redact secrets). I’ll check Call-ID, CSeq, Via, Contact, Record-Route, and SDP content. 1. Enable LiveKit SIP/debug logs ◦ If you can provide LiveKit SIP gateway logs around the time of the call, include them. Look for warnings about unsupported codecs, transaction errors, or malformed SDP. 1. Basic network checks ◦ Confirm Asterisk can reach LiveKit’s SIP gateway IP/port and vice-versa (no firewall blocking ACK). ◦ If NAT exists, ensure Asterisk is sending the correct public IP in SDP or using an external_media_address. What to send me next (if you want me to inspect) • The raw SIP dialog (INVITE, 200 OK, any provisional responses) — headers + SDP — with sensitive creds redacted. • pcap of the call (INVITE → 200 OK → expected ACK window) or Asterisk SIP debug output. • Info about transport (UDP/TCP/TLS), trunk type (chan_sip/pjsip), and whether Asterisk is behind NAT. If you prefer immediate checks, start with a tcpdump on Asterisk when making a test call and paste the 200 OK SIP message here. I’ll look for the specific mismatch that prevents LiveKit from ACKing.
a
It is fixed for incoming to my asterisk. It was localnet issue in asterisk. But it stops RTP when I make outbound to livekit.
t
Thanks — glad the incoming fix is resolved. I understand RTP stops when you make outbound calls to LiveKit. Let’s troubleshoot that so media flows both ways. Quick checklist of likely causes and actions to run now • Codec mismatch ◦ Check SDP in the 200 OK/answer from LiveKit and the SDP Asterisk sends. Ensure a common audio codec (opus, PCMU, PCMA) is present. • RTP ports / firewall ◦ Confirm LiveKit’s RTP ports are reachable from Asterisk. Run a tcpdump on Asterisk side during the call: ▪︎ Capture SIP + RTP:
tcpdump -w outbound_to_livekit.pcap host <livekit_sip_ip> and port 5060 or udp
◦ Verify RTP packets are sent from LiveKit and reach Asterisk, and vice‑versa. • NAT / public IP in SDP ◦ Ensure Asterisk isn’t advertising a private IP in the SDP. If behind NAT, configure external_media_address / rtp_symmetric / stun settings so LiveKit can send RTP back to correct IP:port. • Symmetric RTP / pinholing ◦ If LiveKit sends RTP from a different IP/port than the one Asterisk sent to, Asterisk may drop it unless symmetric RTP is enabled. Enable symmetric RTP or configure static RTP peers. • RTP port ranges and binding ◦ Confirm Asterisk’s RTP port range (rtp.conf) and that those ports aren’t blocked by firewall/NAT. • DTLS/SRTP / Secure media mismatch ◦ If one side expects SRTP/DTLS and the other doesn’t, media will stop. Verify if LiveKit requires secure media for that trunk and match it on Asterisk. • Call state / ACK completeness ◦ Ensure the SIP handshake completes (INVITE → 200 OK → ACK). You said ACK is now present for incoming; verify ACK is seen for outbound flows as well. What to collect and share so I can dig deeper • The 200 OK SDP sent by LiveKit and the SDP Asterisk sends (redact secrets). • A short pcap (tcpdump) covering INVITE → 200 OK → ACK → first 10–20 seconds afterwards. • Asterisk RTP settings: rtp.conf (port range), any NAT settings (external_media_address, rtp_symmetric), and whether you use chan_sip or pjsip. • Whether your trunk/LiveKit uses secure SIP/media (TLS/SRTP) or plain UDP. Immediate quick tests you can run 1. From Asterisk host, run tcpdump while calling and confirm LiveKit RTP packets arrive: ◦
tcpdump -n -s 0 -w sip_rtp.pcap udp and host <livekit_sip_ip>
1. Check Asterisk console for RTP activity and audio levels: ◦
rtp set debug on
or
pjsip set logger on
depending on driver. 1. Temporarily enable symmetric RTP in Asterisk (if using chan_sip/pjsip) and retest. If you prefer, share the 200 OK SDP and a small pcap and I’ll inspect for: • Mismatched codecs • Wrong IP/port in c= or m= lines • RTP packets being sent but dropped/misrouted Which of those logs can you provide first — the 200 OK SDP or a short pcap?
a
It is fixed for incoming to my asterisk. It was localnet issue in asterisk. But it stops RTP when I make outbound to livekit.
t
Thanks — sorry it's still stopping on outbound. I can help diagnose. To focus the troubleshooting, please send one of the following (pick whichever is easiest): • The 200 OK SDP from LiveKit (paste headers + SDP, redact secrets), or • A short pcap (tcpdump) covering INVITE → 200 OK → ACK → first 10–20s of media, or • Your Asterisk RTP/NAT config (rtp.conf,
external_media_address
,
rtp_symmetric
), and whether you use chan_sip or pjsip. While you gather that, please try these quick checks and share results: • tcpdump on Asterisk during the call:
tcpdump -n -s 0 -w outbound_livekit.pcap udp and host <livekit_sip_ip>
— confirm whether LiveKit RTP reaches Asterisk and whether Asterisk sends RTP back. • Verify Asterisk is advertising a public IP in SDP (no private IPs in c= line). • Confirm codec overlap (opus/PCMU/PCMA) present in both SDPs. • Ensure symmetric RTP is enabled (or static peers) so Asterisk accepts RTP from LiveKit if source IP/port differs. Send the 200 OK SDP or the pcap and your Asterisk config and I’ll inspect for mismatched IP/ports, codec issues, SRTP/DTLS mismatch, or transaction problems.
a
When I remove all localnet outbound working but when add for incoming it stops outbound RTP to route on my server.
t
Thanks — good detail. Since outbound RTP breaks only when you add
localnet
for incoming, the root cause is Asterisk advertising/handling local addresses for media. Quick actionable steps to fix and verify: 1. Ensure public IP is advertised in SDP • Set externip (or external_media_address) so Asterisk uses your public IP in SDP. ◦ chan_sip: set
externip=your.public.ip
and keep
localnet=...
◦ pjsip: set
external_media_address
/
external_signaling_address
in transport 1. Enable NAT-friendly behavior • Set
nat=yes
and
force_rport=yes
(chan_sip) or ensure
rport=yes
behavior in pjsip. • Consider
rtp_symmetric=yes
so Asterisk accepts RTP from the source LiveKit actually uses. 1. Disable direct media/hairpinning for those trunks • pjsip:
direct_media=no
(or
canreinvite=no
for chan_sip). This forces Asterisk to relay media and avoids attempts to use private endpoints directly. 1. Confirm RTP port ranges & firewall • Ensure Asterisk RTP port range is open and not overwritten by localnet rules. • Verify no firewall/NAT device rewrites or blocks LiveKit RTP ports. 1. Test and capture: • With localnet configured, capture SIP/SDP + RTP on Asterisk: ◦ tcpdump:
tcpdump -n -s 0 -w trace.pcap udp and host <livekit_sip_ip> or port 5060
• Inspect SDP
c=
and
m=
lines in both directions. If
c=
shows private IP for the media address, that’s the cause. 1. Temporary experiment that often proves the issue • Keep
localnet
present but set
externip
to your public IP and
rtp_symmetric=yes
. If outbound RTP works then, the problem was private SDP addresses. What to send me next if you want deeper inspection • The 200 OK SDP from LiveKit and the SDP Asterisk generates (redact secrets), or • A short pcap covering INVITE → 200 OK → ACK → first 10–20s of RTP. • Your Asterisk RTP/NAT settings (rtp.conf, transport config, and whether chan_sip or pjsip). If you want, I can give the exact config lines for chan_sip or pjsip based on which driver you run. Which driver are you using (chan_sip or pjsip)? Sources: sip | livekit/livekit_composite | DeepWiki | Firewall Tips | How We Built a Globally Distributed WebRTC Mesh Network | Outbound Calling Script | LiveKit docs