Call webhooks
Events for the call lifecycle on phone (PSTN) and WhatsApp numbers, plus call-permission requests.
Events for calls on your numbers, both regular phone (PSTN) and WhatsApp, from ring to hangup. Delivery, retries, idempotency, and signature verification work the same for every event and are covered in the webhooks overview.
Events
| Event | Description |
|---|---|
call.received | Fired when a call is set up: an inbound call (phone/PSTN or WhatsApp) reaching one of your numbers, or an outbound WhatsApp call placed via the API. |
call.ended | Fired when a call (phone/PSTN or WhatsApp) ends; carries duration, end reason, and the cost breakdown. |
call.failed | Fired when a call (phone/PSTN or WhatsApp) fails with a hard error before or during bridging. |
call.permission_request | Fired when a WhatsApp user accepts or rejects your call-permission request. |
Which events fire, in what order
Every call terminates with exactly one of call.ended or call.failed. Whether a call.received precedes it depends on the direction and channel:
| Call | Sequence |
|---|---|
| Inbound, phone or WhatsApp | call.received at ring time → call.ended or call.failed |
| Outbound WhatsApp (via the API) | call.received with direction: "outbound" at origination → call.ended or call.failed |
| Outbound phone (API or browser softphone) | call.ended or call.failed only |
Two details worth wiring into your handler:
call.receivedfires at ring time, before anyone answers. A call nobody picks up still emits it, followed bycall.endedwithendReason: "no_answer".- Unanswered is not failed. No-answer, busy, and rejected calls all terminate with
call.ended(branch onendReason);call.failedis reserved for hard errors before or during bridging.
call.permission_request is independent of any single call: it fires when a WhatsApp user responds to a call-permission request, typically before your first business-initiated call to them.
call.received
Fired when a call is set up on one of your numbers: an inbound call, regular phone (PSTN) or WhatsApp, reaching the number and being routed to its configured destination (an AI voice agent, SIP endpoint, or phone number) — or an outbound WhatsApp call placed via the API, in which case direction is "outbound". The payload carries the Zernio call id, caller and business numbers, the destination snapshot (forwardTo), and contactId/conversationId links into the inbox so you can message the caller, during the call or after, with the regular send APIs. Subscribe with Create webhook settings or Update webhook settings.
Stable webhook event ID
"call.received"The account context included in inbox webhook payloads.
UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.
date-timeResponse Body
call.ended
Fired when a call (phone/PSTN or WhatsApp) ends. Carries durationSeconds, the endReason (hangup, no_answer, rejected, error), and a cost breakdown under call.billing. The recording is not in this payload: it finishes processing a few seconds after hangup and lands on the call record — fetch it with GET /v1/calls/{id}/recording using the call.id from this event. The classic automation here is a missed-call follow-up: on endReason: "no_answer", text the caller back over the linked conversationId. Subscribe with Create webhook settings or Update webhook settings.
"call.ended"The account context included in inbox webhook payloads.
UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.
date-timeResponse Body
call.failed
Fired when a call (phone/PSTN or WhatsApp) fails with a hard error before or during bridging (for example, the destination rejected the SIP INVITE). Carries the platform error code and message. Subscribe with Create webhook settings or Update webhook settings.
"call.failed"The account context included in inbox webhook payloads.
UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.
date-timeResponse Body
call.permission_request
Fired when a WhatsApp user responds to your call-permission request (business-initiated calls require one). Carries the user's number, their response (accept or reject), whether it is permanent, and the expiry when it is not. Subscribe with Create webhook settings or Update webhook settings.
"call.permission_request"The account context included in inbox webhook payloads.
UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.
date-time