The WebSocket transport connects CXB Core to the WebSocket telephony dialler. Calls arrive as WebSocket connections carrying audio and control events in a custom JSON protocol.

Connection flow

Endpoint

WebSocket /ws/{bot_id}
bot_id identifies which bot configuration to fetch from CXB API.

Audio format

PropertyValue
EncodingLINEAR16 (signed 16-bit PCM)
Sample rate8,000 Hz
ChannelsMono
Transport encodingBase64 in JSON payload field

Handshake events

The dialler sends exactly three events after connection:
First event. Contains caller metadata.
FieldTypeDescription
eventstring"connected"
callerIdstringCaller’s phone number
didstringDialled number (DID)
callDirectionstring"incoming" or "outgoing"
streamIdstringStream identifier (optional, may come in start)
All fields from the connected event (minus event) are forwarded to CXB API as connected_event query params during config fetch.
Second event. Contains stream and media format info.
FieldTypeDescription
eventstring"start"
streamIdstringStream identifier
mediaFormatobjectAudio format metadata
Third event. Signals handshake complete, pipeline can start.
FieldTypeDescription
eventstring"answer"

Protocol events

Incoming (dialler → CXB Core)

EventDescription
mediaAudio frame. payload field contains base64-encoded LINEAR16 PCM.
hangup-callCustomer or network disconnection. disconnectedBy field indicates source.

Outgoing (CXB Core → dialler)

EventDescription
reverse-mediaBot audio response. Base64-encoded LINEAR16 PCM in payload.
reverse-media-stopClears the dialler’s audio buffer. Sent before hangup.
reverse-hangup-callDrops the call. Sent after reverse-media-stop.
reverse-call-transferTransfers the call to a specified number.

reverse-media payload

{
  "event": "reverse-media",
  "chunk": 42,
  "did": "+911234567890",
  "payload": "<base64 audio>",
  "timestamp": "2026-04-24 14:30:00",
  "streamId": "stream-abc",
  "callerId": "+919876543210",
  "chunk_durn_ms": 20,
  "callDirection": "incoming",
  "encoding": "LINEAR",
  "source": "ai"
}

Hangup behavior

Bot-initiated hangup:
  1. CXB Core sends reverse-media-stop (clears audio buffer)
  2. CXB Core sends reverse-hangup-call
  3. disconnected_by = "bot"
Customer hangup:
  1. Dialler sends hangup-call event
  2. CXB Core receives CancelFrame, pipeline winds down
  3. disconnected_by = "customer"

Transfer behavior

When the bot transfers the call, CXB Core sends a single reverse-call-transfer event (transfer method websocket_reverse_transfer) carrying the destination transferno. Transfer is terminal for the bot leg: CXB Core sets the serializer’s _hangup_sent flag so the EndFrame auto-hangup does not fire a reverse-hangup-call after the transfer.
{
  "event": "reverse-call-transfer",
  "streamId": "stream-abc",
  "callerId": "+919876543210",
  "did": "+911234567890",
  "transferno": "+911112223333",
  "source": "ai"
}

Outside active hours

If CXB API returns no config (bot unavailable or outside its active-hours window), fetch_config() returns None. CXB Core drops the call immediately by sending reverse-media-stop followed by reverse-hangup-call to the dialler, and records disconnected_by = "outside_hours".

Capacity

When a worker is at MAX_CONCURRENT_CALLS, new WebSocket connections are rejected with close code 1008 (“Server at capacity”). The nginx least_conn balancer routes to the least-loaded worker.