The WebSocket transport’s telephony dialler emits a Call Detail Record (CDR) at the end of a call, carrying carrier-side facts (talk time, call status, recording filename) that the media pipeline does not have. CXB API merges this CDR onto the matching call document. The route handler is receive_cdr().
POST /api/v1/cdr
X-CXBCore-Secret: {secret}
Content-Type: application/json
The endpoint requires X-CXBCore-Secret (403 on mismatch). The CDR is matched to a call by streamId / stream_id, which was captured on the call skeleton at config time.

Merge behaviour

The handler reads the raw JSON body and looks up streamId (falling back to stream_id). It then updates the call document keyed on stream_id:
Stored fieldSource key in CDR
cdrthe full raw CDR body
cdr_talktimetalktime
cdr_call_statuscall_status
cdr_recording_urlrecording_filename

Responses

ConditionResponse
No streamId in body{ status: ignored, reason: "no streamId" }
No call matches stream_id{ status: not_found, stream_id }
Merged{ status: ok, stream_id }
This route is WebSocket-transport specific. LiveKit SIP and Exotel transports do not post CDRs here — their carrier-side facts come through other paths. The CDR merge is additive: it does not change the call’s status or post-call analysis, only attaches the cdr_* fields.

Operational checks

SymptomFirst place to check
CDR returns not_foundstream_id mismatch between config-time skeleton and the CDR.
CDR returns ignoredDialler sent a body without streamId.
403X-CXBCore-Secret mismatch.
Missing carrier talk timeWhether the dialler actually posted a CDR for this stream_id.

Results ingestion

The primary post-call result intake from CXB Core.

WebSocket transport

The transport that produces these CDRs.

Recordings proxy

Serving recordings referenced by call records.