# Receiving webhooks

Register a webhook on a campaign to hear when a prospect replies, and handle deliveries, retries and duplicates.

Instead of polling for replies, register a webhook on a campaign. When a prospect replies, Victoria AI sends a [`prospect_response`](https://docs.versionseven.ai/api-reference/webhooks/prospect-response) event to your URL, with the reply, the lead, the campaign, and the AI Appointment Setter's read of the reply.

## 1. Build an endpoint

Your endpoint must:

- Accept `POST` requests with a JSON body over **HTTPS**, at an address reachable from the public internet.
- Respond with a `2xx` status within 75 seconds. Acknowledge the delivery first, then do slow work such as CRM updates.
- Check the `X-Signature-256` header before trusting the body. See [Verifying signatures](https://docs.versionseven.ai/guides/verifying-webhooks).

To build and test it before a live campaign sends anything, use the sample deliveries from [`GET /v1/webhooks/examples`](https://docs.versionseven.ai/api-reference/reference/list-webhook-examples).

## 2. Register the webhook

Register your URL on a campaign with [`POST /v1/campaigns/{campaign_id}/webhooks`](https://docs.versionseven.ai/api-reference/campaigns/create-webhook). Send a `secret` of at least 16 characters to sign deliveries with a value you already hold, or leave it out and one is generated:

```bash
curl -X POST https://api.versionseven.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/webhooks \
  -H "Authorization: Bearer $VICTORIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url": "https://example.com/victoria/webhooks"}'
```

The response includes the `webhook_id`, and the `secret` when one was generated.

> **Warning:** A generated secret is returned only in this response. Store it before you discard the response. If you lose it, set a new one with [`POST /v1/campaigns/{campaign_id}/webhooks/{webhook_id}/secret`](https://docs.versionseven.ai/api-reference/campaigns/rotate-webhook-secret).

| Response | When |
| - | - |
| `201` | The webhook was created. |
| `200` | A disabled webhook for the same URL was enabled again. |
| `409 DUPLICATE_WEBHOOK` | The URL is already active on this campaign. |
| `400 INVALID_WEBHOOK_URL` | The URL isn't HTTPS, or doesn't resolve to a public address. |

A campaign can have several webhooks, and each delivery goes to all of them at the same time.

A lead's reply is normally delivered once per campaign, not once per message. The exception is described under [Retries and duplicates](https://docs.versionseven.ai/guides/webhooks#retries-and-duplicates).

## 3. Handle deliveries

Each delivery is a `POST` with `Content-Type: application/json`. A shortened example:

```json
{
  "event": "prospect_response",
  "idempotency_key": "f3a1c9d2b8e7460a91d0a25f3e7b4c1d6f8a92e0b4c1d6f8a92e0b4c1d6f8a92",
  "sequence_lead_id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2025-01-14T19:30:00+00:00",
  "campaign": "Q1 2024 Outbound Campaign",
  "channel": "email",
  "prospect_message": "Hi, I'm interested in learning more about your product.",
  "ai_response": {
    "sentiment": "positive",
    "out_of_office": false,
    "sdr_brief": "Prospect expressed interest, recommend scheduling demo within 24 hours."
  }
}
```

The [event reference](https://docs.versionseven.ai/api-reference/webhooks/prospect-response) documents every field, including the lead, the sending account and the rest of `ai_response`.

## Retries and duplicates

- A delivery fails when your endpoint answers with an error status or doesn't respond within 75 seconds.
- If every webhook on the campaign fails, the delivery is retried every 15 minutes, up to 5 attempts within 48 hours.
- A retry carries the same `idempotency_key` as the original delivery. Record the keys you've processed and skip any you've already seen.
- A later reply from the same lead can arrive as a new event with a new `idempotency_key`, for example when a prospect who first asked a question then says yes.

## Managing webhooks

| Task | Endpoint |
| - | - |
| List a campaign's webhooks | [`GET /v1/campaigns/{campaign_id}/webhooks`](https://docs.versionseven.ai/api-reference/campaigns/list-webhooks) |
| Stop deliveries to a webhook | [`DELETE /v1/campaigns/{campaign_id}/webhooks/{webhook_id}`](https://docs.versionseven.ai/api-reference/campaigns/delete-webhook) |
| Set or rotate the signing secret | [`POST /v1/campaigns/{campaign_id}/webhooks/{webhook_id}/secret`](https://docs.versionseven.ai/api-reference/campaigns/rotate-webhook-secret) |

Deleting a webhook disables it rather than destroying it. Registering the same URL again re-enables it, along with its existing signing secret.
