When CXB Core finishes a call, it posts the full result payload to CXB API. CXB API stores it onto the existing call record, then fans out to campaign reconciliation and (optionally) the CRM post-call push. The route handler is receive_call_results(); storage logic lives in the call service.
POST /api/v1/call-results
X-CXBCore-Secret: {secret}
Content-Type: application/json
The endpoint requires X-CXBCore-Secret (403 on mismatch). The body is validated against the CallResultsPayload model and dumped with exclude_none=True before storage, so absent fields never overwrite the call skeleton with nulls.

Payload

CallResultsPayload carries the final call state. Key fields:
FieldNotes
session_id, stream_id, caller_id, from_numberIdentity / linkage (matched on session_id).
call_duration_seconds, disconnected_by, call_directionOutcome basics.
transcript, events, intentsConversation timeline.
recording_url, recording_key, recording_access_tokenRecording references.
post_call_analysis, qc_analysisRaw LLM dicts (schema defined by the bot prompt).
latency, latency_samples, usage_metrics, llm_cache_summaryTelemetry / cost.
pipeline_config, connected_eventDiagnostic snapshots.
was_transferred, transfer_*Native transfer state derived by CXB Core.

Storage

store_call_results() updates db.calls by session_id (upsert) and:
  • Recording token: if a recording is present but no recording_access_token, it reuses the existing call’s token or mints a new secrets.token_urlsafe(24). This token backs the stable CRM-facing recording URL.
  • Status derivation from disconnected_by:
disconnected_byStored status
error, no_answer, rejectedfailed
voicemailvoicemail
(anything else)completed
  • Cache summary: build_llm_cache_summary() aggregates post_call/qc usage entries into token-only facts (cache_read_input_tokens, cache_creation_input_tokens, fresh_prompt_tokens, completion_tokens, plus enabled/worked/statuses). It never reports money.
  • Sets completed_at.
A connected_event of None is popped before storage so it never clobbers the connected event captured on the call skeleton at config time.

Fan-out

After storage, the call doc is reloaded by session_id and two background tasks may be scheduled:
  1. Campaign reconciliationprocess_campaign_call_result runs for every call (it no-ops when the call is not campaign-linked). See Campaign API and Callbacks.
  2. CRM post-call push — runs only when the bot has a post_call_push config and the call is not flagged skip_post_push. It passes the bot timezone, system minio config, and the request’s public base URL. See CRM push.
The endpoint returns { "status": "ok", "session_id": ... } immediately; the fan-out runs in FastAPI background tasks.

Operational checks

SymptomFirst place to check
Result not storedX-CXBCore-Secret, payload validation errors, CXB Core result outbox.
Call stuck activeResult webhook never delivered (CXB Core outbox), or wrong webhook_url.
Wrong statusdisconnected_by value mapping above.
No CRM pushBot post_call_push config, skip_post_push flag, _skip_post_push control flag at config time.
Recording link missing on pushrecording_access_token / recording_key on the call doc.

CXB Core post-call

What CXB Core packages before posting results.

CRM push

Mapping results into the external CRM payload.

CDR ingestion

Merging WebSocket-transport CDRs into call records.

Recordings proxy

Token-based stable recording URLs.