receive_cdr().
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 upstreamId (falling back to stream_id). It then updates the call document keyed on stream_id:
| Stored field | Source key in CDR |
|---|---|
cdr | the full raw CDR body |
cdr_talktime | talktime |
cdr_call_status | call_status |
cdr_recording_url | recording_filename |
Responses
| Condition | Response |
|---|---|
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
| Symptom | First place to check |
|---|---|
CDR returns not_found | stream_id mismatch between config-time skeleton and the CDR. |
CDR returns ignored | Dialler sent a body without streamId. |
403 | X-CXBCore-Secret mismatch. |
| Missing carrier talk time | Whether the dialler actually posted a CDR for this stream_id. |
Related docs
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.