Webhook Payloads
This page documents the structure of webhook payloads sent by Vocobase when events occur.Headers
Every webhook request includes these headers:Event types
Branch on the top-levelevent field. These are the values accepted in an
endpoint’s events allowlist — see
Scoping & Event Filtering.
Six event types, and that is the complete list.
GET /api/v2/config/webhooks
returns the same set as data.available_events, so you can read it from the API
rather than hard-coding it.
webhook.test is fired only by the test endpoint and is not a valid
allowlist value — it always reaches the endpoint being tested. Handle or ignore
it explicitly.
How a failed session surfaces
A session that ends in a terminal failure — carrier no-answer, busy, rejected, or a bot that never started — is delivered assession.completed, not as a
separate failure event. Every session-terminal path builds its payload through
the same builder, which always emits event: "session.completed", including the
explicit failure path.
Branch on the payload, not on the event name:
data.call.status is the reliable discriminator for telephony sessions. For
WebRTC sessions there is no call object at all, so use duration_seconds and
transcript.
inbound.route (below) is a pre-answer routing callback with its own headers
and a required response body. It is not part of this event stream and cannot be
subscribed to.
Event: webhook.test
Sent by POST /api/v2/config/webhooks/{id}/test. Signed and retried exactly
like a real event, so receiving it confirms your URL, TLS, and signature
verification all work end to end.
agent_id is the endpoint’s scope: null for an account-wide endpoint, the
agent’s id for an agent-scoped one.
Event: inbound.route
This event is sent when an account-configured pre-answer policy is available for a Vobiz or Plivo inbound call.
Payload structure
Headers
Inbound route webhooks for this feature use feature-specific headers:Response contract
Accounts return JSON with anaction value:
ANSWER_WITH_AISELECT_AGENTTRANSFERREJECT
TRANSFER, to_number must be E.164.
When action is SELECT_AGENT, agent_id must be set.
Vocobase records each resolved route decision for your own observability. Query GET /api/v2/inbound-route-decisions to inspect action, source, webhook status, webhook latency, failure reason, transfer target, and created session or phone-call IDs.
Event: call.status.updated
Sent whenever Vocobase learns that a telephony call moved to a new meaningful call status. This event is for real-time orchestration: live dashboards, your own dialer queues, and concurrency release.
call.status.updated is sent for phone calls only. Browser WebRTC sessions do not have a carrier call leg and therefore do not emit this event.
Payload structure
Field reference
Call status values
Vocobase normalizes provider-specific call states into one vocabulary:Provider normalization
Accounts should branch ondata.status, not on provider-specific values. Vocobase stores the raw provider value for debugging.
External queue orchestration
If you run your own dialer, usecall.status.updated to manage slots:
session.completed to release a dialer slot. session.completed is the final rich result after transcript, recording, billing, and extraction processing.
Event: session.completed
Sent when a Vocobase session has reached final post-call processing. For telephony calls, this happens after the call leg ends and the bot/session pipeline has persisted transcript, recording metadata, billing, variables, and extraction results.
Payload structure
Field reference
Data object fields
Transcript entry format
Each entry in thetranscript array has:
recording_url is a presigned URL valid for 7 days from the moment the webhook fires. Download and persist the recording on your side if you need long-term retention. If the URL expires before you can download it, call GET /calls/{call_id}/recording-url to mint a fresh one with another 7-day window.Hangup attribution coverage
hangup_source and hangup_cause come from the carrier’s Call Detail Record, which we fetch after the hangup callback. Not every carrier exposes one. On carriers that do not, both fields are null on every call — including calls that ended perfectly normally.
Treat null as “this carrier does not report it,” never as “nobody hung up” and never as an error. In particular, do not infer an abandoned or failed call from a null hangup_source; use call.disposition for outcome logic and hangup_source only as supporting detail when present.
"unknown" is distinct from null: it means the carrier answered the question and did not know. Both are non-actionable, but only null means the signal was never available.
If your account needs teardown attribution and you are seeing null across the board, contact support — the coverage gap is per-carrier, and switching the number’s provider is usually what changes it.
Final call status on session.completed
call.status in session.completed reflects the final known carrier state. For real-time intermediate statuses, use call.status.updated.
Call disposition values
Transferred calls
When an agent transfers a live call to a human:- The bot speaks the configured announcement (
transfer_message). - Vocobase transfers the caller to
transfer_target. - The
session.completedwebhook includes transfer metadata.
session.completed webhook fires once the bot leg ends, not when the caller eventually hangs up with the human. This means:
duration_secondsreflects only the bot leg (caller-on-with-bot time).recording_url, when present, is the bot-leg recording. The human side is not recorded.call.transferredistrue;call.transferred_atis the redirect moment;call.transfer_targetis the destination E.164.
call.transferred as the cheap discriminator if your downstream automation needs to distinguish a transferred call from a normal hangup.
Event: call.transferred
Fired the moment a live call is handed off to a human, from the transfer route
on a successful transfer. The call is still in progress when this fires, so
there is no duration, disposition, transcript, or recording yet —
transferred_at and transfer_target are the payload.
Emitted for phone calls only. Browser WebRTC sessions cannot be transferred.
Payload structure
Field reference
call.transferred does not replace session.completed. A transferred call
still emits session.completed when the bot leg ends, with
call.transferred: true. Use call.transferred when you need to react at
hand-off time; use session.completed for the final record.Events: whatsapp.message.*
Three events cover outbound WhatsApp messaging. They share one payload shape and
differ only in the event value and the data.status that triggered them.
Payload structure
Field reference
data.status is uppercase here, unlike call.status.updated and
session.completed, whose call statuses are lowercased. Compare
case-insensitively if you normalize statuses across event types.Polling GET /calls/{id} vs webhook
Both the webhook and GET /api/v2/calls/{id} return the same data shape, so an account that can’t accept inbound webhooks (or wants to reconcile dropped deliveries) can poll the GET endpoint instead.
A call can appear completed before all transcript, recording, and extraction fields are ready. The processing_complete boolean tells you when the response is final:
Recommended polling pattern
session.completed webhook will reach you before polling kicks in — polling is the fallback for accounts that can’t accept inbound webhooks, or for reconciliation after a delivery failure.
For sessions created before this field shipped,
pipeline_completed_at will be null permanently. Treat null + status: completed on an older row as “final” — those rows pre-date the signal.Post-call extraction
When the agent has a Custom Analysis config defined, the platform runs an LLM extraction over the transcript after every completed call and ships the result inline in the samesession.completed webhook — no second webhook to handle. Configure extraction via the dashboard’s “Variables & Analytics” tab on the agent editor or via PUT /api/v2/agent/:id with extraction_config.
Extraction object structure
Extraction failures never block webhook delivery. The unified payload always ships with
extraction.status indicating outcome — accounts that need extraction values can branch on status === 'success', accounts that just need transcript + recording can ignore the field.Replay extraction against a past session
If you editextraction_config and want to backfill an existing session, call POST /api/v2/calls/{call_id}/extract with { "dry_run": false }. Optional dry_run: true returns the result without persisting. The replay uses the agent’s current config (not the config at original-call time).
Pre-call variables
Thevariables field on every webhook echoes the per-call values that were substituted into the agent’s prompt and greeting. Configure variable names via the dashboard “Variables & Analytics” tab or PUT /api/v2/agent/:id with variables: ["callee_name", "mobile_number"]. Supply per-call values via POST /api/v2/calls/start body field variables: { callee_name: "Sajal" }. Missing values render as empty strings — the platform never errors on missing variables.
Next steps
Webhook Setup
Configure webhook endpoints and verify signatures.
Error Handling
Understand retry behavior and failure handling.