> ## 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.vaani.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.vaani.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.completed",
  "call_id": "call-789-xyz",
  "campaign_id": "abc-123-xyz",
  "campaign_name": "Summer Promo 2024",
  "contact": {
    "name": "John Doe",
    "phone": "+919876543210"
  },
  "call_status": "completed",
  "call_duration_seconds": 87,
  "call_outcome": "interested",
  "call_tags": ["qualified", "follow_up_needed"],
  "transcript": "Agent: Hello, this is...\nCustomer: Hi...",
  "summary": "Customer expressed interest in the product...",
  "timestamp": "2026-07-01T14:35:22Z"
}
```

***

## 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.
</Warning>

***

## 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.vaani.ai/api/campaigns/create" \
  -F "campaign_webhook_url=https://abc123.ngrok.io/webhooks/campaign"
```

***

## Common Issues

<AccordionGroup>
  <Accordion title="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>

  <Accordion title="Receiving duplicate events">
    * Implement idempotency using event IDs
    * Store processed event IDs in database
    * Return 200 for duplicate events
  </Accordion>

  <Accordion title="Webhook timing out">
    * Process events asynchronously (queue-based)
    * Respond with 200 immediately, then process
    * Keep endpoint response time \< 5 seconds
  </Accordion>
</AccordionGroup>
