Skip to main content

Overview

Vaani provides two types of webhooks for campaign monitoring:
  1. Campaign-Level Webhooks - Triggered on campaign lifecycle events (started, paused, resumed, completed, report ready)
  2. Per-Call Webhooks - Triggered after each call completes post-processing
Webhooks are delivered once (10 second timeout) and are not retried. If your endpoint is down or returns an error, that event is lost. Webhooks set through the API via campaign_webhook_url / per_call_webhook_url are also not signed (see Webhook Security). Use Get Campaign Status and Get Campaign Results to reconcile anything you miss.

Campaign-Level Webhooks

Notifies you when a campaign changes state.

Configuration

Provide campaign_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

Provide per_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:
The digest is HMAC-SHA256(secret, raw request body), hex encoded. There is no timestamp header and the timestamp is not part of the signed string.
A signature is only sent when the webhook has a secret. Webhooks supplied through the API with campaign_webhook_url or per_call_webhook_url have no secret, so those requests arrive unsigned. Do not reject requests that lack the header unless you have configured a secret for that webhook in the dashboard. Restrict your endpoint by an unguessable URL path or token query parameter instead.
Python Verification Example:
Always compute the HMAC over the raw bytes you received, before JSON parsing.

Webhook Endpoint Requirements

Your webhook endpoint must:
  1. Respond quickly (< 10 seconds, ideally < 2) or the delivery is abandoned
  2. Return 2xx status to acknowledge receipt
  3. Be publicly accessible over HTTPS
  4. 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

  • 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
  • A state can legitimately fire the same event more than once (for example repeated campaign_paused)
  • Key on call_id for call_postprocessing
  • Return 200 for duplicates
  • Process events asynchronously (queue-based)
  • Respond with 200 immediately, then process
  • Keep endpoint response time well under 10 seconds