Skip to main content

What is a Webhook?

A webhook lets Vaani push call events to your server in real time - no polling required. You’ll receive an HTTP POST request to your endpoint whenever a call lifecycle event occurs (e.g. call started, ended, transferred, or post-processed).

Event Types

Vaani fires webhooks for both outbound and inbound calls.

Setup

1

Create a Webhook Endpoint

Set up an HTTP endpoint on your server that accepts POST requests and returns 200 OK.
2

Register the Webhook in the Dashboard

Go to Settings → Webhooks and add your endpoint URL.
Webhook setup screen
Your webhook endpoint must be publicly accessible. For local development, use a tunnel like ngrok.

Delivery

Signatures are only sent when the webhook has a secret. Always respond with a 2xx quickly and make your handler idempotent.

Payload Reference

Every event is delivered as a JSON object with the same outer envelope. Event-specific fields live inside data:
to and from are the called and calling numbers: for an outbound call to is the user and from is your agent’s number, for an inbound call it is the other way round. If the webhook has a secret, the request carries an X-Webhook-Signature header with the HMAC-SHA256 hex digest of the raw body (no sha256= prefix for these events). The per-event examples below show the complete delivered payload for each event.
call_postprocessing is sent by a different component than the other events and has a slightly different envelope (shown at the end of this page), with call_id, timestamp and data at the top level. Its signature header is X-Webhook-Signature: sha256=<hex>.

Outbound Call Events

call_started

Fired as soon as the SIP participant joins the room and the call begins dialing. For inbound and WebRTC calls the data carries caller instead of phone_number, and call_type is Inbound, WebRTC_Inbound or WebRTC_Outbound.

call_ringing

Fired when the carrier reports the phone is actively ringing at the destination.

Lifecycle Success Events

user_picked_up_at

Fired when the user answers and media starts flowing.

Lifecycle Terminal Errors (Outbound Only)

call_rejected

Fired if the user manually rejects the call.

call_no_answer

Fired if the call is not answered. reason is USER_UNAVAILABLE when the carrier reports the user unavailable, or TIMEOUT when nobody picks up within the ~45 second ring timeout.

call_failed

Fired for SIP errors, technical failures or when the line ends in a failed state. reason is SIP_ERROR or INTERNAL_ERROR (both also carry error_code and error_message), or the final call status reported by the carrier.
When the carrier reports the line as busy, the agent sends call_user_busy (same data as above, with reason set to the call status) instead of call_failed. call_user_busy is not on the list of events you can subscribe to in the dashboard, so only integrations whose webhook accepts it will see it.

Human Agent Transfer Events

summary in these events is the context summary the AI agent passes to the human.

human_transfer_initiated

Fired when the AI agent triggers the transfer_call tool to hand off the session.

human_transfer_successful

Fired when the human specialist answers and the AI agent is ready to introduce the call (warm transfers).

human_transfer_failed

Fired if the transfer cannot be completed. reason is rejected_or_busy (with sip_status) for a warm transfer the human did not accept, cold_transfer_failed or transfer_error (with error) otherwise.

Session Termination & Analysis

call_ended

Fired as soon as the room is disconnected and the session terminates.
call_duration in this event is expressed in seconds.
end_reason is a descriptive string, not a fixed enum. Observed values include "Call ended" (normal hang-up) and "User went silent - Call Timed out"; do not match on an exact list. technical_issue is true when the call ended because of an error on our side or the carrier.

call_postprocessing

Fired after the session ends and the backend has completed transcription, summary generation, and entity extraction.
call_duration in this event is expressed in seconds (a float rounded to 2 decimals), the same unit as call_ended. transcript and summary are inside data.
call_postprocessing is sent only for calls that went through analysis. It is not sent when the call was too short (under 1 second), had no transcript, the user never spoke (for example end_reason: "User went silent - Call Timed out"), or the call never connected (no answer, rejected, failed). Use call_ended (or Get Call Details) for those calls. entities and dispositions are null unless the agent has data extraction / dispositions configured. recording_url is a signed link that ends in ?token=...; use it as returned.