Skip to content

Webhooks

Receive call lifecycle events as they happen

View .md

Voice Agents emit webhook events across a call's lifecycle. Subscribe to them on a sender webhook to log conversations, trigger follow-ups, or update your systems in real time.

Configure the events the same way as any other sender webhook — see Webhooks for setup, and Security to verify signatures. Voice events are signed exactly like message.* events, with the X-Zavu-Signature header.

Events

EventWhen it firesdata.status
call.initiatedAn outbound call is dialing, or an inbound call is received.ringing
call.answeredThe call is answered and the agent is connected.in_progress
call.completedThe call ended after a conversation.completed
call.failedThe call could not be completed (busy, no answer, canceled, or error).busy, no_answer, canceled, or failed

Subscribe to call.completed and call.failed to handle outcomes. Add call.initiated and call.answered when you need live progress.

Payload Structure

All voice events share the standard webhook envelope. The data object carries the call fields.

JSON
{ "id": "evt_1705312200000_call123", "type": "call.completed", "timestamp": 1705312200000, "senderId": "snd_abc123", "projectId": "prj_xyz789", "data": { "callId": "call_abc123", "direction": "inbound", "from": "+14155551234", "to": "+13125559876", "status": "completed", "durationSeconds": 84, "endReason": "agent_ended", "transcriptAvailable": true } }

Data Fields

FieldTypeDescription
callIdstringThe call identifier. Use it to fetch the full call and transcript.
directionstringinbound or outbound.
fromstringCaller number in E.164 format.
tostringCallee number in E.164 format.
statusstringCall status for this event.
durationSecondsnumber | nullConnected talk time in seconds. null before the call is answered.
endReasonstring | nullWhy the call ended (e.g. agent_ended, max_duration, transfer, hangup). Present on call.completed and call.failed.
transcriptAvailablebooleanWhether a transcript can be fetched for this call.

call.initiated

Fired when an outbound call starts dialing or an inbound call arrives.

JSON
{ "id": "evt_1705312200000_init123", "type": "call.initiated", "timestamp": 1705312200000, "senderId": "snd_abc123", "projectId": "prj_xyz789", "data": { "callId": "call_abc123", "direction": "outbound", "from": "+13125559876", "to": "+14155551234", "status": "ringing", "durationSeconds": null, "endReason": null, "transcriptAvailable": false } }

call.answered

Fired when the call connects and the agent begins the conversation.

JSON
{ "id": "evt_1705312210000_ans123", "type": "call.answered", "timestamp": 1705312210000, "senderId": "snd_abc123", "projectId": "prj_xyz789", "data": { "callId": "call_abc123", "direction": "outbound", "from": "+13125559876", "to": "+14155551234", "status": "in_progress", "durationSeconds": null, "endReason": null, "transcriptAvailable": false } }

call.completed

Fired when a call ends after a conversation. Fetch the call to read the transcript.

JSON
{ "id": "evt_1705312300000_done123", "type": "call.completed", "timestamp": 1705312300000, "senderId": "snd_abc123", "projectId": "prj_xyz789", "data": { "callId": "call_abc123", "direction": "outbound", "from": "+13125559876", "to": "+14155551234", "status": "completed", "durationSeconds": 92, "endReason": "agent_ended", "transcriptAvailable": true } }

call.failed

Fired when a call could not be completed. Check status and endReason for the cause.

JSON
{ "id": "evt_1705312260000_fail123", "type": "call.failed", "timestamp": 1705312260000, "senderId": "snd_abc123", "projectId": "prj_xyz789", "data": { "callId": "call_abc123", "direction": "outbound", "from": "+13125559876", "to": "+14155551234", "status": "no_answer", "durationSeconds": null, "endReason": "no_answer", "transcriptAvailable": false } }

Example Handler

Verify the signature, acknowledge quickly, and process asynchronously — the same pattern as message webhooks.

async function handleVoiceEvent(event) { const { callId, status, direction, durationSeconds } = event.data; switch (event.type) { case "call.completed": { // Fetch the transcript for records or analysis. The voice endpoints are // not in the SDK yet, so call the API directly. const res = await fetch(`https://api.zavu.dev/v1/calls/${callId}`, { headers: { Authorization: `Bearer ${process.env.ZAVUDEV_API_KEY}` }, }); const { call } = await res.json(); await db.calls.save({ callId, direction, durationSeconds, transcript: call.transcript, }); break; } case "call.failed": await db.calls.markFailed(callId, event.data.endReason); break; } }

Next Steps