Skip to main content
This is the technical reference for VoiceInfra webhooks — the events, the payload, and signature verification. For configuring your endpoint and reading delivery logs, see the Webhooks setup guide.

Delivery

VoiceInfra sends an HTTP POST to the URL configured on your Webhooks page when a call ends.
Respond quickly, and move slow work off the request path. A handler that takes longer than the timeout counts as a failure and gets retried.

Events

There are two. call_failed covers a specific set of outcomes: an error, a concurrency limit, insufficient credit, a missing agent, a session error, an unanswered call, a rejected call, a SIP trunk failure, or a lost connection. Calls logged as rejected-call are always call_failed, whatever else they report.
Both events use the same payload shape. Branch on the event field rather than assuming one.

Request headers

The event type is not sent as a header. Read it from the event field in the payload.

Payload

Every payload has the same envelope: identifying fields at the top level, and the call itself under data.

Envelope

string
call_completed or call_failed.
string
A unique ID for this delivery. Use it to deduplicate if you receive the same event twice.
string
ISO 8601 timestamp for when the event was raised.
string
The workspace the call belongs to.

data

string | null
The call log entry for this call. Use it to fetch the full record from the portal or the call-log endpoints.
string | null
The agent that handled the call.
string | null
That agent’s name, so you do not have to look it up.
string
web-call, sip-inbound-call, sip-outbound-call, chat-session, or rejected-call.
integer | null
Call duration in seconds.
string | null
The caller’s number.
string | null
The number that was called.
string
A human-readable reason the call ended — for example “Caller hung up” or “Maximum duration reached”.
string
When the call started. Note this sits inside data and is separate from the envelope’s timestamp, which is when the event fired.
array | null
The conversation transcript.
object | null
The AI-generated summary and metadata. null when analysis is off or the agent is privacy-protected.
object | null
Data the agent extracted during the call, following the schema you defined on the agent.
string | null
A signed URL for the recording, valid for one hour. null when recording is disabled.
object | null
The cost breakdown for the call.
object | null
Pre-call form values, or the per-contact variables passed for an outbound call.
string | null
Set when the call came from a batch campaign.

Example

call_completed
call_recording_url expires one hour after it is generated. If you need to keep the recording, download it when the webhook arrives rather than storing the URL.

Retries

A 4xx response stops delivery permanently. If your endpoint returns 401 because signature verification failed, the event is dropped — nothing is retried, and you will not see it again. Always return 2xx once you have accepted a payload, and investigate failures through the delivery logs.

Verifying the signature

When your workspace has a webhook secret, every delivery is signed with HMAC-SHA256 over the raw request body, and the signature is sent in X-VoiceInfra-Signature as a hex string. To verify:
  1. Read the raw request body as bytes, before any JSON parsing.
  2. Compute HMAC-SHA256(raw_body, your_webhook_secret) and hex-encode it.
  3. Compare with the header using a constant-time comparison.
  4. Reject with 401 if they do not match.
Python
Node.js
In Express, use express.raw({ type: 'application/json' }) rather than express.json(), so the raw bytes are still available at verification time. Once a body has been parsed and re-serialised, the signature will not match.

Delivery logs

Your Webhooks page lists recent deliveries with the time, the event, the HTTP status your endpoint returned, and the call it relates to. Open any delivery for the full detail, including the error message when a delivery failed. Use the logs to diagnose failures and to check your signature verification — a delivery returning 401 is almost always a verification bug on your side, and because 4xx is not retried, those events are gone.