> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vaanivoice.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Campaign Webhooks

> Real-time notifications for campaign events and call completions

## Overview

Vaani provides two types of webhooks for campaign monitoring:

1. **Campaign-Level Webhooks** - Triggered on campaign status changes
2. **Per-Call Webhooks** - Triggered after each call completes post-processing

***

## Campaign-Level Webhooks

Notifies you when campaign status changes (`active`, `paused`, `completed`, `cancelled`).

### Configuration

Provide `campaign_webhook_url` when creating or updating a campaign:

```bash theme={null}
curl -X POST "https://api.vaanivoice.ai/api/campaigns/create" \
  -H "X-API-Key: vaani_abc123..." \
  -F "campaign_webhook_url=https://your-domain.com/webhooks/campaign"
```

### Payload

```json theme={null}
{
  "event": "campaign.status_changed",
  "campaign_id": "abc-123-xyz",
  "campaign_name": "Summer Promo 2024",
  "old_status": "active",
  "new_status": "paused",
  "timestamp": "2026-07-01T14:30:00Z",
  "stats": {
    "total_calls": 523,
    "completed": 498,
    "pending": 477,
    "in_progress": 3,
    "failed": 12
  }
}
```

***

## 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:

```bash theme={null}
curl -X POST "https://api.vaanivoice.ai/api/campaigns/create" \
  -H "X-API-Key: vaani_abc123..." \
  -F "per_call_webhook_url=https://your-domain.com/webhooks/call"
```

### Payload

```json theme={null}
{
  "event": "call_postprocessing",
  "call_id": "outbound-1785148654-d915171f",
  "campaign_id": "d079211d-7519-4441-988c-368ed9c90812",
  "campaign_name": "Test Campaign",
  "timestamp": "2026-07-27T10:38:36.588135+00:00",
  "data": {
    "room_name": "outbound-1785148654-d915171f",
    "call_id": "outbound-1785148654-d915171f",
    "call_duration": 21651.399,
    "end_reason": "Call ended",
    "summary": "The agent greeted the customer and offered assistance after detecting inactivity. The customer did not respond, so no issue was identified or addressed. No next steps were established due to lack of engagement.",
    "entities": null,
    "dispositions": null,
    "recording_url": "https://mohammad-bk.vaaniresearch.com/api/stream/outbound-1785148654-d915171f",
    "transcript": "[10:37:59] AGENT: Hi! you have reached our customer service, I am your assistant, how can I help you today?\n\n[10:38:08] AGENT: Hello, you have been inactive for a while, Please let me know if you need help,"
  }
}
```

***

## Webhook Retry Logic

If your webhook endpoint is unreachable or returns a non-2xx status, Vaani automatically retries with exponential backoff:

| Attempt | Delay |
| - | - |
| 1 | Immediate |
| 2 | 5 seconds |
| 3 | 25 seconds |
| 4 | 125 seconds (\~2 min) |
| 5 | 625 seconds (\~10 min) |

After 5 failed attempts, the webhook is marked as failed and no further retries occur.

***

## Webhook Security

### Verify Webhook Signature

Vaani signs all webhook payloads with HMAC-SHA256. Verify the signature before processing:

**Request Headers:**

```
X-Vaani-Signature: sha256=abc123...
X-Vaani-Timestamp: 1719842400
```

**Python Verification Example:**

```python theme={null}
import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str, timestamp: str):
    # Prevent replay attacks (check timestamp is within 5 minutes)
    if abs(int(time.time()) - int(timestamp)) > 300:
        return False
    
    # Compute expected signature
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.{payload.decode()}".encode(),
        hashlib.sha256
    ).hexdigest()
    
    # Compare signatures securely
    return hmac.compare_digest(f"sha256={expected}", signature)
```

> [!WARNING]
>
> Always verify webhook signatures in production to prevent spoofing attacks.

***

## Webhook Endpoint Requirements

Your webhook endpoint must:

1. **Respond quickly** (\< 5 seconds) to avoid timeouts
2. **Return 2xx status** to acknowledge receipt
3. **Be publicly accessible** over HTTPS
4. **Handle duplicate events** (use event IDs for idempotency)

### Example Flask Endpoint

```python theme={null}
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhooks/campaign', methods=['POST'])
def campaign_webhook():
    payload = request.get_json()
    
    # Verify signature (recommended)
    signature = request.headers.get('X-Vaani-Signature')
    timestamp = request.headers.get('X-Vaani-Timestamp')
    if not verify_webhook(request.data, signature, YOUR_SECRET, timestamp):
        return jsonify({"error": "Invalid signature"}), 401
    
    # Process event
    if payload['event'] == 'campaign.status_changed':
        campaign_id = payload['campaign_id']
        new_status = payload['new_status']
        print(f"Campaign {campaign_id} changed to {new_status}")
    
    return jsonify({"status": "received"}), 200
```

***

## Event Types Reference

### Campaign Events

| Event | Description |
| - | - |
| `campaign.status_changed` | Campaign status changed |
| `campaign.completed` | All calls finished |
| `campaign.cancelled` | Campaign was cancelled |

### Call Events

| Event | Description |
| - | - |
| `call.completed` | Call finished and processed |
| `call.failed` | Call attempt failed |
| `call.no_answer` | Recipient didn't answer |

***

## Testing Webhooks

Use tools like [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) to test webhooks locally:

```bash theme={null}
# 1. Start ngrok tunnel
ngrok http 5000

# 2. Use the ngrok URL in your campaign
curl -X POST "https://api.vaanivoice.ai/api/campaigns/create" \
  -F "campaign_webhook_url=https://abc123.ngrok.io/webhooks/campaign"
```

***

## Common Issues

> [accordion] Webhook not receiving events
>
> * Ensure URL is publicly accessible over HTTPS
> * Check firewall/security group rules
> * Verify endpoint returns 2xx status
> * Check Vaani logs for delivery attempts

> [accordion] Receiving duplicate events
>
> * Implement idempotency using event IDs
> * Store processed event IDs in database
> * Return 200 for duplicate events

> [accordion] Webhook timing out
>
> * Process events asynchronously (queue-based)
> * Respond with 200 immediately, then process
> * Keep endpoint response time \< 5 seconds


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.