Our setup: - We are using a self-hosted LiveKit...
# ask-ai
l
Our setup: - We are using a self-hosted LiveKit server. - We are also using a self-hosted SIP server. - The SIP trunk is configured to call a Twilio PSTN number. What works: - The same SIP trunk credentials work correctly in the LiveKit cloud SIP server. - In our self-hosted setup, the LiveKit room and SIP participant are created successfully. - Media negotiation also starts, so the RTC side appears healthy. What happens in our self-hosted setup: - When we trigger a call, the mobile number rings. - In some cases, the phone receives multiple incoming call attempts. - Each attempt ends automatically after a short time. - The logs show Twilio returning a 407 auth challenge. - After that, the SIP invite fails and LiveKit eventually returns request timed out / 408. What we observed in logs: - SIP participant creation starts successfully. - Twilio sends 407 during the SIP invite flow. - The invite is then canceled / fails. - LiveKit reports the SIP participant request timed out. What we have already checked: - The trunk username is configured correctly. - The same trunk credentials work with the cloud SIP server. - So the issue seems specific to our self-hosted SIP server or its auth flow. Question: - Is there anything special we need to configure in the self-hosted SIP server to handle Twilio’s 407 challenge correctly? - Could the self-hosted SIP server be failing to send the correct password or digest auth response? - Why would the call be retried multiple times and create multiple ring attempts on the mobile?
✅ 1
t
Thanks for reaching out, Rajadurai. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — this is helpful detail. I’ll respond to your three questions and give focused troubleshooting steps. Short answers • Yes — your self-hosted SIP side must correctly handle Twilio’s 407 (Proxy Authentication Required) by generating and sending the proper Digest Authorization header on the same INVITE transaction. • Yes — the self-hosted SIP server can fail if it doesn’t compute or attach the correct digest (username, realm, nonce, response MD5, etc.). • Multiple ring attempts happen because the trunk or SIP proxy is retransmitting new INVITEs (or forking) when the 407 isn’t handled inline, so each retry appears as a separate call attempt to the PSTN endpoint. What to check and try (order matters) 1. Confirm transport/security ◦ Ensure your SIP signaling to Twilio uses the expected transport (TLS if required) and certificates are valid. Twilio commonly requires TLS for trunking. 1. Verify digest auth handling ◦ Ensure your SIP server: ▪︎ Parses the 407/Proxy-Authenticate challenge fields (realm, nonce, qop, algorithm). ▪︎ Generates the Digest
Authorization
(or
Proxy-Authorization
) header with correct MD5 response using the configured
auth_username
and
auth_password
. ▪︎ Sends the re-INVITE (or re-sends the INVITE with Authorization) within the same transaction/dialog rather than starting a new one. 1. Transaction and retransmission behavior ◦ Confirm your SIP implementation handles authentication within the same client transaction (not spawning a new INVITE each time). ◦ Check SIP timers and retransmit logic; excessive timeout before resending could cause Twilio or your PSTN to treat retries as separate calls. 1. Headers and proxy behavior ◦ Ensure you send Authorization as
Proxy-Authorization
if the 407 comes from a proxy, or
Authorization
if from the UA/endpoint — match what Twilio’s 407 indicates. ◦ Preserve required headers (Via, Call-ID, CSeq) when responding to challenge so the re-INVITE matches the original transaction. 1. Compare cloud vs self-hosted flows ◦ Capture SIP traces for both cloud (working) and self-hosted (failing) flows and compare: ▪︎ The initial INVITE, 407 challenge, and the subsequent Authorized INVITE. ▪︎ Differences in Authorization/Proxy-Authorization headers, CSeq, Call-ID, and whether the re-INVITE uses the same transaction identifiers. 1. Common causes seen in practice ◦ Missing or incorrect
auth_password
usage when computing MD5 response. ◦ Using the wrong header name (
Authorization
vs
Proxy-Authorization
). ◦ Re-INVITE issued as a new call (new Call-ID/CSeq), causing Twilio to treat it as a fresh attempt. ◦ Not handling qop/nonce/stale logic from Twilio’s challenge. Minimal debugging steps to run now 1. Enable full SIP logging (show INVITE/407/INVITE-with-Authorization) on the self-hosted SIP service. 2. Capture a failing call trace and redact secrets. Look at: ◦ 407 body (realm, nonce, qop). ◦ The immediate next request from your server — does it include a correctly formed Authorization/Proxy-Authorization header and same Call-ID/CSeq? 1. If the Authorization header is missing or malformed, confirm how your server reads `auth_username`/`auth_password` and where it computes the digest. If you want, paste a redacted (remove passwords) example of the INVITE → 407 → next INVITE sequence and I’ll point out any header/transaction mismatches and exact fields to fix. Sources: Agents telephony integration | LiveKit Documentation | Layered Caller Verification for LiveKit SIP Calls | LiveKit | Layered Caller Verification for LiveKit SIP Calls | LiveKit | SIP Telephony and Encryption, with interactive elements
l
how to check Headers and proxy behavior
t
Good — I’ll walk you through how to capture and inspect SIP headers and proxy behavior so you can spot the 407 flow problem. 1) Enable SIP debug logging • Turn on full SIP debug in your SIP server/service (or the LiveKit SIP component) so INVITE/407/Authorization messages are logged. Look for options like “sip trace”, “debug sip”, or “pjsip set logger on” depending on your server. 2) Capture packet-level traces • Run a packet capture on the SIP signaling interface (pcap/tcpdump). Example command: ◦ sudo tcpdump -i interface -s 0 -w sip_trace.pcap port 5060 or port 5061 • If using TLS, capture the pcap anyway; you can still see SIP headers if not encrypted, or use server-side TLS keys to decrypt if available. 3) Reproduce the failing call while tracing • Trigger a call so the INVITE → 407 → next request sequence is recorded. 4) Inspect the trace (Wireshark or text) • Open the pcap in Wireshark or convert SIP logs to text. Filter by SIP (sip) or by Call-ID. • Look at these messages in order: ◦ Initial INVITE from your SIP server to Twilio ◦ Twilio’s 407 Proxy Authentication Required response — note the Proxy-Authenticate header fields:
realm
,
nonce
,
qop
,
algorithm
,
stale
◦ The next request your server sends after the 407 (should be an INVITE with Authorization/Proxy-Authorization) 5) What to verify in headers • Call-ID, CSeq, Via: The re-sent INVITE must preserve Call-ID and increment CSeq appropriately (CSeq SHOULD be incremented for new requests in same dialog) and include the same Via branch if it’s the same client transaction. • Authorization header: ◦ If 407 was returned, the client must send
Proxy-Authorization:
with fields
username
,
realm
,
nonce
,
uri
,
response
(MD5 hash), and if present
qop
,
nc
,
cnonce
. ◦ If the 407 came from a proxy, use `Proxy-Authorization`; if from the endpoint, use
Authorization
. • Response correctness: Validate the
response
value matches MD5(HA1nonceHA2) per RFC 2617 (tools/online calculators can help for quick checks). HA1 = MD5(usernamerealmpassword), HA2 = MD5(method:uri). • qop handling: If
qop
is present in the challenge, ensure your server sets
qop
,
nc
, and
cnonce
correctly and includes them in the response. 6) Signs of problems to spot • No Authorization/Proxy-Authorization header sent after 407. • Authorization header present but
response
value incorrect (wrong MD5 / wrong password / wrong realm). • Wrong header name (
Authorization
vs
Proxy-Authorization
) relative to the 407 type. • New Call-ID or completely new INVITE (indicates spawning a new transaction rather than within same dialog). • Missing or changed Via branch or Contact headers that break transaction matching. • Multiple INVITEs with new Call-IDs — explains multiple rings. 7) Example Wireshark workflow • Filter: sip && ip.addr == twilio-ip (or sip.Call-ID == "callid") • Follow: Right-click a SIP packet → Follow → UDP/TCP Stream or use “Telephony → SIP Flows” to see the sequence. 8) If TLS hides headers • If signaling is TLS-encrypted and you cannot decrypt, enable application-level SIP debug on your SIP service to log the post-TLS SIP text; packet capture alone won’t show headers. If you want, share a redacted (remove passwords, tokens) copy of: • the initial INVITE, • the 407 response, • the subsequent INVITE from your server. I’ll point out exactly which header(s) or field(s) look wrong and what to change. , with interactive elements
Thanks — quick follow-up. Could you paste a redacted SIP trace of the INVITE → 407 → next request sequence (remove passwords, tokens, IPs if needed)? Specifically include the three messages: • Initial INVITE • Twilio’s 407 (Proxy-Authenticate) • The next request your server sends If you can’t share traces, tell me: • Whether the subsequent request includes an
Authorization
or
Proxy-Authorization
header, and • Whether Call-ID, CSeq, and Via branch are preserved or replaced. I’ll point out exactly which headers/fields to fix. , with interactive elements
Thanks — the logs confirm Twilio sent a 407 and LiveKit’s outbound INVITE flow didn’t complete the auth exchange, leading to timeout. Quick next checks I recommend (please run these and share results or redacted traces): 1) Confirm which header your server sends after 407 • Does the subsequent request include
Proxy-Authorization
or
Authorization
? Tell me which. 2) Verify transaction identifiers are preserved • Check Call-ID and CSeq between the initial INVITE and the follow-up request. Are they the same Call-ID and is CSeq incremented (same method but higher number)? Tell me the values or confirm match. 3) Show the Proxy-Authenticate challenge fields • From the 407, capture
realm
,
nonce
,
qop
, and
algorithm
. Paste them (redact nonce if you prefer). 4) If possible, paste the redacted three-message sequence (Initial INVITE → 407 → Your next request). Remove passwords and IPs. I’ll point out exact header mismatches. If you can’t share traces, answer 1–3 and I’ll identify the most likely fix (Authorization vs Proxy-Authorization, wrong/missing response MD5, new Call-ID spawning retries). , with interactive elements
Thanks — this capture shows the INVITE, provisional responses, and the CANCEL/487s, but it doesn’t include the 407 challenge or the subsequent INVITE with Authorization. To diagnose the auth failure I need a redacted copy of the actual 407 and the next request your server sent. Please do the following and share the redacted lines: 1. Capture the full SIP exchange for a failing call (INVITE → 407 → next request). If using tcpdump, include enough packets (increase capture length) and filter for the Call‑ID or the two IPs so the 407 is included. 2. From that capture paste the three messages (remove passwords, nonces if desired): ◦ Initial INVITE (full headers) ◦ Twilio’s 407 (show the Proxy-Authenticate header: realm, nonce, qop, algorithm) ◦ The immediate next request your server sent (show Authorization/Proxy-Authorization header and Call-ID/CSeq/Via) If you can’t share the trace, answer these instead: • Does the next request include an
Authorization
or
Proxy-Authorization
header? (which one) • Are Call-ID and CSeq preserved between the initial INVITE and the next request? (same Call-ID, CSeq incremented) • What are the
realm
and
qop
values from the 407 (you can redact nonce)? With those three messages or those answers I’ll pinpoint whether the server is using the wrong header, computing the wrong digest, or spawning a new transaction (which causes the multiple rings). , with interactive elements
l
I found that call is succefully intiated when wait_until_answered= False. no call terminated and call is connecting when answered
t
Great find — thanks for sharing that result. I'll explain why disabling
wait_until_answered
makes the call succeed and what to do next. Why disabling wait_until_answered helped • With
wait_until_answered = False
, LiveKit does not block on completing the full SIP invite/answer handshake before proceeding. That avoids the timeout path where your self-hosted SIP side fails to complete the 407 digest challenge exchange in time, so the call proceeds and connects when the PSTN answers. • The root issue remains that the 407 challenge exchange is not being completed inline (or quickly enough) by your self-hosted SIP stack, so LiveKit times out when it waits for the authenticated response. Recommended next steps (pick one or both) 1) Fix 407/digest handling (preferred) • Ensure your SIP component: ◦ Receives Twilio’s 407 Proxy-Authenticate (realm, nonce, qop). ◦ Computes the digest (HA1 = MD5(usernamerealmpassword); HA2 = MD5(method:uri); response per RFC) and sends the re-INVITE with
Proxy-Authorization
(or
Authorization
if the 407 is from the UA) within the same transaction (same Call-ID, incremented CSeq). • Enable SIP debug, capture INVITE→407→authorized INVITE and confirm: ◦ Authorization header is present and uses
Proxy-Authorization
if 407 is proxy-originated. ◦ Call-ID is preserved and CSeq is incremented (not a fresh new INVITE). • Fix any mismatch (wrong header name, missing qop/nc/cnonce handling, incorrect MD5 input, or creating a new transaction). 2) Keep using wait_until_answered = False as a temporary workaround • This avoids the timeout by not waiting for the authenticated answer, but it masks the underlying auth problem and may affect call control/UX (e.g., media/connect timing). • Use only while you implement the digest/auth fixes. What I can help with next • If you want to fix auth: paste a redacted INVITE → 407 → next request and I’ll point out the exact header or field mismatch. • If you prefer the workaround: I can list any trade-offs to watch for when using
wait_until_answered = False
. Which would you like to do next? , with interactive elements