post_call_push config and a key_mapper. Orchestration is run_post_call_push() in the call service; the HTTP send is push_post_call_data() in the CRM service.
Trigger
The push is scheduled from results ingestion only when the bot has apost_call_push config and the call is not flagged skip_post_push. It runs as a background task with the bot timezone, system minio config, and the request’s public base URL.
Recording URL substitution
Before building variables,run_post_call_push rewrites the recording reference:
- If a
recording_key+recording_access_token+ public base URL are all available → use the stable token URL (/api/v1/public/recordings/{token}). - Otherwise, if a
recording_url+minioconfig exist → generate a signed URL as a fallback (for older call docs without a token).
Variable assembly
build_push_variables(call_doc, payload, tz_name) builds the full {{namespace.field}} context the key mapper resolves against:
| Namespace | Source |
|---|---|
call.* | Stored connected_event + filtered SIP-header variables. Also call.sip_headers_json (all SIP headers as one JSON object). |
crm.* | The pre-call CRM fetch (crm_data). |
result.* | System-generated: call_id, recording_url, call_duration_seconds, disconnected_by, disposition, start/end/answer timestamps (bot timezone), transcript (formatted JSON), and the full transfer_* set. |
analysis.* | LLM post-call analysis. All known keys are pre-seeded to "" so unresolved placeholders never leak as literals. |
qc.* | LLM QC analysis (empathy_score, compliance_score, overall_score, remarks), pre-seeded to "". |
result.disposition is a coarse system label derived from disconnected_by (customer/bot/timeout → Connected, error → Failed, else Unknown) — distinct from the LLM analysis.DISPOSITION. result.call_answer_time equals the start time for the WebSocket transport, which does not report answer time separately.Template resolution and drop_unresolved
push_post_call_data renders every key_mapper template with render_template(..., drop_unresolved=True):
- Resolution tries the exact
{{ns.field}}key, then a suffix fallback acrosssystem.→crm.→call.by priority. So{{CUSTOMERNAME}}can resolve fromcrm.CUSTOMERNAME, and{{crm.X}}can fall back tocall.X. - With
drop_unresolved=True, an unresolved{{...}}becomes""instead of being preserved as a literal — so a missing variable sends an empty string rather than leaking the template to the CRM. _coerce_json_likeparses values that look like JSON ({...}/[...]) back into objects/arrays, so a mapped field likecallingheaderscan be sent as a real object when the CRM requires it.
Send and logging
Setting (in post_call_push) | Use |
|---|---|
url | Destination (no url → push skipped). |
method | POST (JSON body) or GET (query params). Default POST. |
token / headers | headers if provided, else Authorization: {token}. |
key_mapper | { external_key: "{{template}}" } map producing the payload. |
status = ok/rejected/skipped/failed, http_status, truncated response_body) is stored on the call doc as post_call_push_result, appended to integration_log, and a post_call_push event is pushed onto events with a relative timestamp. Request timeout is 10s.
The pre-call CRM fetch (
fetch_crm_data in the CRM service) is a separate path with a 2s timeout, used at config time to populate crm.*. This page covers the post-call push direction.Operational checks
| Symptom | First place to check |
|---|---|
| Push not sent | Bot post_call_push.url, skip_post_push flag. |
| CRM rejects payload | key_mapper keys vs CRM schema, post_call_push_result.response_body. |
Literal {{...}} reached CRM | Should not happen under drop_unresolved; check the mapper key prefix. |
| Field sent as string instead of object | _coerce_json_like only parses values starting with {/[. |
| Recording link broken at CRM | Token URL vs signed URL fallback; see Recordings proxy. |
Related docs
Post-call variables
Full key_mapper variable catalog.
Results ingestion
What triggers the push.
Recordings proxy
Stable recording URL substitution.
Post-call webhook
Operator-facing webhook configuration.