Overview
Vaani provides two types of webhooks for campaign monitoring:- Campaign-Level Webhooks - Triggered on campaign lifecycle events (started, paused, resumed, completed, report ready)
- Per-Call Webhooks - Triggered after each call completes post-processing
Campaign-Level Webhooks
Notifies you when a campaign changes state.Configuration
Providecampaign_webhook_url when creating or updating a campaign:
Payload
The
campaign_report_ready event is sent once the results file has been generated and adds three fields:
results_url is a pre-signed download link that is valid for 24 hours.
Per-Call Webhooks
Notifies you after each call completes and transcript/summary are generated.Configuration
Provideper_call_webhook_url when creating or updating a campaign:
Payload
call_duration is in seconds (a float, rounded to 2 decimals). transcript and summary are inside data, not at the top level. transcript is a plain-text string (or null).call_postprocessing is not sent for calls that were too short, had no transcript, where the contact never spoke, or that never connected (no answer, rejected, failed). Such contacts still appear in the campaign results file. recording_url is a signed link ending in ?token=....Delivery
Make your endpoint idempotent (key on
call_id, or on campaign_id + event) and respond with a 2xx quickly.
Webhook Security
When a webhook has a signing secret, Vaani adds one header:HMAC-SHA256(secret, raw request body), hex encoded. There is no timestamp header and the timestamp is not part of the signed string.
Python Verification Example:
Webhook Endpoint Requirements
Your webhook endpoint must:- Respond quickly (< 10 seconds, ideally < 2) or the delivery is abandoned
- Return 2xx status to acknowledge receipt
- Be publicly accessible over HTTPS
- Handle duplicate or missing events (there are no retries; reconcile with the status/results endpoints)
Example Flask Endpoint
Event Types Reference
Campaign Events
There is no event for cancellation.
/cancel sends only a campaign_report_ready event. campaign_paused is sent only when the status actually changes, so pausing an already paused campaign sends nothing.Call Events
Other call-lifecycle events (
call_started, call_ended, call_failed, call_no_answer, …) exist for webhooks configured in the dashboard, but they are not sent to the URLs you pass in the campaign API. Names with a dot (campaign.status_changed, call.completed) do not exist.Testing Webhooks
Use tools like webhook.site or ngrok to test webhooks locally:Common Issues
Webhook not receiving events
Webhook not receiving events
- Ensure URL is publicly accessible over HTTPS
- Check firewall/security group rules
- Verify endpoint returns 2xx status quickly (the 10 second timeout is not retried)
- Remember that only the events listed above are sent to the campaign API URLs
Receiving duplicate events
Receiving duplicate events
- A state can legitimately fire the same event more than once (for example repeated
campaign_paused) - Key on
call_idforcall_postprocessing - Return 200 for duplicates
Webhook timing out
Webhook timing out
- Process events asynchronously (queue-based)
- Respond with 200 immediately, then process
- Keep endpoint response time well under 10 seconds

