Webhooks
Webhooks are the primary channel for results; pollingGET /v1/calls/{call_id} is the fallback. An administrator registers the endpoint on the agent: open the agent in the editor → Settings tab → section Webhooks (post-call). The payload contents are also described on the Webhooks page.
- URL —
http://orhttps://(the platform does not enforce HTTPS; use it anyway). Delivery is subject to the platform’s outbound host policy: private addresses and disallowed hosts may be refused by the operator’s configuration, and such deliveries fail into the dead-letter queue. - Event subscription — any subset of the three events below (default:
call_ended+call_analyzed). - Signing secret — configured on the platform side as a reference to a server-side secret (the name of an environment variable of the platform deployment); the value itself is never entered in the dashboard and is shared with you out of band.
- Agent — an endpoint is bound to exactly one live agent of the organization. The field is mandatory: an empty agent name is refused with
webhooks.agent_required, an unknown or archived one withwebhooks.agent_not_found. There is no organization-wide level — to cover several agents, register the endpoint on each of them (the same URL may be reused;agent_idin the payload tells them apart). - Test events — an opt-in flag makes the endpoint also receive events of editor test sessions (
mode: "test"). - Timeout — how long the platform waits for your
2xx(platform default 10 s, configurable per endpoint up to 120 s;0means the platform default).
Event envelope
Every event is an HTTP(S)POST with a JSON body (UTF-8, non-ASCII characters unescaped).
Headers on every delivery:
Which event arrives when
call_ended— as soon as the session finishes, before analysis and usually before the recording is finalized. This is the early notification: transcript,call_status,status_reason,error_code, and the timing fields (answered_at,media_started_at,talk_from,talk_to).latencyis present only when per-turn metrics were collected; its keys are the raw metric names ending in_ms(turn_ms,llm_ms,asr_ms,tts_ms, …) with values{avg, max}in milliseconds.call_analyzed— when both post-call pipelines are done: the analysis (terminal in any ofdone,skipped,failed,interrupted— the event is always delivered) and the recording finalization. The event is self-contained: full call block + analysis + ready recording. If you only need the final outcome, subscribe tocall_analyzedalone and skipcall_ended. Itscallblock is identical tocall_ended.callexcept that the recording stays top-level and is not duplicated insidecall.observer_incident— during the call, when a compliance observer configured on the agent detects a violation and its reaction includes a notification. Use it for real-time supervisor alerts.severityislow|medium|high(an unknown value is normalized tomedium),confidenceis 0–1 ornull,modemay be""when unknown. Multiple incidents in one call produce separate events; retries of the same incident keep the sameevent_id.ping— a test delivery from the dashboard button;agent_idis the agent the endpoint is bound to (""for an organization-level endpoint). Respond2xxand verify its signature like a real event. It is not retried.
analysis_status is anything but done, call_analysis is {} and a top-level reason field explains why (for example voicemail, no_speech, analytics_disabled); analytics_tier, analytics_model_used and analytics_language are empty strings then.
Delivery semantics
- At-least-once. A delivery counts as successful only on a
2xxfrom your endpoint; anything else (non-2xx, timeout, connection error) is retried. You will receive duplicates — deduplicate onevent_id. - Retries with exponential backoff — platform settings, not per endpoint: by default up to 8 attempts with pauses of 30, 60, 120, 240, 480, 960 and 1920 seconds. After the final failed attempt the event moves to a dead-letter queue, from which an administrator can redeliver it manually (History → session details → webhook deliveries). Dead deliveries are purged after a retention period, so the manual-retry window is finite.
- Deliveries always go to the endpoint’s current URL, so a mistyped URL can be corrected and the event redelivered; an endpoint that was deleted or disabled sends its pending events straight to the dead-letter queue.
- Ordering is best-effort.
call_endedis enqueued beforecall_analyzed, but delivery order is not guaranteed;observer_incidentarrives mid-call. Treat each event independently, correlating bycall_id. - Respond fast. Return
2xximmediately after signature verification and process asynchronously: the platform waits at most the endpoint’s timeout.
Signature verification
X-CFlow-Timestamp is an integer number of Unix seconds. Verify on the raw bytes of the body (before any JSON parsing or re-serialization — the body is UTF-8 with unescaped non-ASCII characters), compare in constant time, and reject stale timestamps — a 300-second window. Retried deliveries are re-signed with a fresh timestamp, so the window does not conflict with retries.
request.get_data(); in Node (Express) use express.raw({ type: "application/json" }) — both preserve the exact bytes the signature was computed over.
Recordings
recording_url is a stable URL (https://{{HUBTALK_FQDN}}/api/recordings/rec-…): it does not expire and can be persisted in your systems. Each GET to it answers 307 with a fresh short-lived download link, so a URL saved months ago keeps working — until the recording itself is deleted by the retention policy (recording.expires_at). Other answers: 409 while the recording is not ready yet (keep polling), 410 after deletion, 503 when the storage is temporarily unavailable. Append ?download=1 for an attachment disposition. The URL is unauthenticated but unguessable; treat it as a capability token.
One edge case: if the recording takes unusually long to finalize (longer than the platform’s wait, 30 s by default), call_analyzed is still emitted — with the current non-terminal recording status and an empty recording_url. The stable URL starts working on its own once the recording is ready.