The LiveKit SIP inbound transport handles incoming calls routed through LiveKit SIP trunks. A phone call arrives at a DID, LiveKit creates a room, and dispatches CXB Core to join.
Connection flow
Endpoint
Requires the X-CXB-Core-Secret header. Requests without a valid secret are rejected with 403.
Request body
{
"room_name": "sip-room-abc",
"caller_number": "+919876543210",
"dialed_number": "+911234567890",
"call_id": "optional-uuid"
}
| Field | Type | Required | Description |
|---|
room_name | string | Yes | LiveKit room name created by the SIP dispatch rule |
caller_number | string | Yes | Caller’s phone number |
dialed_number | string | Yes | DID that was called — used to look up bot ID |
call_id | string | No | Optional call ID. Generated if not provided. |
Response
{
"status": "accepted",
"call_id": "uuid-string"
}
The endpoint returns immediately. The pipeline runs in a background task.
Error codes
| Status | Meaning |
|---|
| 200 | Accepted — pipeline spawned in background |
| 403 | Unauthorized — missing or invalid X-CXB-Core-Secret |
| 429 | At capacity — worker has no free slot. The inbound SIP participant is removed before returning. |
| 404 | No bot mapped to dialed_number. The inbound SIP participant is removed and the slot released. |
| 503 | LiveKit not configured (LIVEKIT_URL unset) |
The 429 and 404 paths remove the inbound SIP participant as a side effect. This is why the fleet nginx 429-retry block must not be applied to /livekit/dispatch — a retry would tear down a leg that another worker can no longer serve.
| Property | Value |
|---|
| Encoding | LINEAR16 (signed 16-bit PCM) |
| Sample rate | 16,000 Hz |
| Channels | Mono |
| Transport | LiveKit SDK (WebRTC) |
Bot ID lookup
CXB Core resolves the bot ID from the dialled number by calling CXB API:
GET {cxb_api_url}/sip/lookup?number={dialed_number}
Returns {"bot_id": "..."}. If no bot is mapped to the DID, the call is rejected with 404.
Before fetching config, CXB Core enriches the connected_event it sends to CXB API:
- SIP X-header capture —
_load_sip_candidate_headers() reads the inbound SIP participant’s attributes via the LiveKit API and runs extract_sip_candidate_headers(). Any captured headers are attached as connected_event.sip_candidate_headers, so carrier-supplied X-headers (campaign IDs, account refs) become available to the bot. This depends on the inbound trunk declaring headers_to_attributes — LiveKit drops SIP X-headers otherwise.
- DID passthrough —
dialed_number is forwarded into post-call results as from_number, identifying which DID the caller reached.
Opening-message audio gating
/livekit/dispatch passes audio_ready_event_name="on_audio_track_subscribed" into the pipeline factory. on_first_participant_joined only signals that the SIP participant has a signal connection — the RTP media path can take another 1-2s to negotiate. If the bot spoke during that window the caller would miss the opening words.
The factory waits for on_audio_track_subscribed (which means the media path is bidirectional) before queueing the opening message, with a 5-second timeout fallback so a missing event never deadlocks the call.
Agent Desk handoff
When the bot escalates to a human agent (rather than a SIP transfer), the call takes the Agent Desk path instead of transfer_sip_participant: CXB Core enqueues a durable handoff to CXB API and starts a hold worker that keeps the caller engaged while an agent is found. A successful enqueue sets disconnected_by = "transfer_to_agent", and the hard route timeout deliberately does not tear down a call that has been handed off. This path is wired through the transfer_call tool / agent_desk_callback.
Hangup behavior
Bot-initiated: CXB Core calls transport.disconnect(), which leaves the LiveKit room. LiveKit SIP infrastructure tears down the phone call.
Customer hangup: LiveKit fires on_participant_left. CXB Core captures disconnected_by = "customer" and winds down the pipeline.
Transfer: CXB Core calls the LiveKit SIP transfer API (lkapi.sip.transfer_sip_participant()) to issue a SIP REFER to another number (transfer method livekit_sip_refer).
On inbound, the REFER carries SIP X-headers from config.sip_context.transfer_headers (passed as the headers argument). This lets the transfer target receive carrier/context headers on the new leg. Header passthrough depends on the trunk declaring headers_to_attributes — LiveKit strips X-headers otherwise.
Prerequisites
- LiveKit server with SIP trunks configured
- SIP dispatch rule pointing to CXB Core’s
/livekit/dispatch endpoint
- DID → bot ID mapping in CXB API
- Environment variables:
LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET