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

POST /livekit/dispatch
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"
}
FieldTypeRequiredDescription
room_namestringYesLiveKit room name created by the SIP dispatch rule
caller_numberstringYesCaller’s phone number
dialed_numberstringYesDID that was called — used to look up bot ID
call_idstringNoOptional 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

StatusMeaning
200Accepted — pipeline spawned in background
403Unauthorized — missing or invalid X-CXB-Core-Secret
429At capacity — worker has no free slot. The inbound SIP participant is removed before returning.
404No bot mapped to dialed_number. The inbound SIP participant is removed and the slot released.
503LiveKit 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.

Audio format

PropertyValue
EncodingLINEAR16 (signed 16-bit PCM)
Sample rate16,000 Hz
ChannelsMono
TransportLiveKit 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.

Inbound metadata

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 passthroughdialed_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