---
title: "Webhooks"
description: "Get a signed request from Chatvox when a lead is captured, a visitor asks for a person, or a Helpdesk ticket is opened."
---

:::note[Plan]
Webhooks are available on the **Standard** plan and above. Only team **Owners** and **Admins** can manage them.
:::

A webhook is a URL on your server, or in a tool like Zapier, Make, or n8n. When an event happens, Chatvox sends it a `POST` request with the details as JSON.

## Add a webhook

1. **Open API & webhooks**

    Go to **Team settings**. Next to **API & webhooks**, select **Manage**, then **Add webhook**.

2. **Enter your endpoint**

    Enter the URL that receives events. It must use `https://` and be reachable from the internet.

3. **Choose the agent**

    - **All agents**: events from every agent in the team, including agents you create later.
    - **One agent**: only that agent's events. Use this to send each agent's events somewhere different, for example Agent A's leads to Zapier and Agent B's to n8n.

4. **Choose events**

    Tick the [events](#events) you want to receive.

5. **Save the signing secret**

    After you save, Chatvox shows the webhook's **signing secret** once. It starts with `whsec_`. Store it on your server and use it to [verify requests](/docs/developers/verifying-webhooks).

6. **Send a test event**

    Open the webhook's **…** menu and select **Send test event**. Then open **Deliveries** to check that your endpoint answered.

You can add as many webhooks as you need. An event goes to every active webhook that's subscribed to it and covers that agent.

## Events

| Event | Sent when | `data` contains |
| --- | --- | --- |
| `lead.created` | An agent captures a new lead. | `lead` |
| `lead.updated` | A lead gets new details later in the same conversation. | `lead` |
| `conversation.handoff_requested` | A visitor or the agent asks for a person (website, Public Chat, or WhatsApp). | `conversation`, `reason`, `summary` |
| `helpdesk_ticket.opened` | A new [Helpdesk ticket](/docs/human-handoff/tickets) is opened. | `ticket` |
| `webhook.test` | You select **Send test event** on an active webhook. Always sent, whatever events you chose. | `message` |

## Payload

Every request has the same envelope:

| Field | Description |
| --- | --- |
| `id` | A unique ID for this event. Same as the `webhook-id` header. |
| `type` | The event name, such as `lead.created`. |
| `created_at` | When the event happened. |
| `team_id` | Your team's ID. |
| `agent_id` | The agent the event came from. For test events, the webhook's agent, or `null` for an **All agents** webhook. |
| `data` | The event's details. |

### `lead.created` and `lead.updated`

`data.lead` is a [lead object](/docs/developers/leads#the-lead-object).

```json
{
  "id": "4b1b9e0e-2f6c-4a7e-9c55-0a2f6c1d9e10",
  "type": "lead.created",
  "created_at": "2026-10-09T18:40:12+00:00",
  "team_id": "019c83c8-fc1b-705b-ae7c-64ca1e3c7158",
  "agent_id": "019f80ad-e549-712d-bc26-3ab29ae2228c",
  "data": {
    "lead": {
      "id": "01a12267-9e4a-73b6-8619-2bb0cdda7ba2",
      "agent_id": "019f80ad-e549-712d-bc26-3ab29ae2228c",
      "conversation_id": "01a12260-2b5c-7d41-9f0e-6a1f2c9b8e11",
      "name": "Ada Lovelace",
      "email": "ada@example.com",
      "phone": "+1 555 0100",
      "notes": null,
      "custom_fields": { "company": "Analytical Engines Ltd" },
      "source": "embed",
      "referrer_url": "https://example.com/pricing",
      "utm": { "utm_source": "google", "utm_campaign": "spring_sale" },
      "created_at": "2026-10-09T18:40:12+00:00",
      "updated_at": "2026-10-09T18:40:12+00:00"
    }
  }
}
```

### `conversation.handoff_requested`

`data.conversation` is a [conversation object](/docs/developers/conversations#the-conversation-object) without messages. `reason` and `summary` explain why a person was requested, when available.

```json
{
  "id": "…",
  "type": "conversation.handoff_requested",
  "created_at": "2026-10-09T19:58:02+00:00",
  "team_id": "…",
  "agent_id": "…",
  "data": {
    "conversation": { "id": "…", "state": "handoff_requested", "source": "public_link", "…": "…" },
    "reason": "The customer wants to change their billing address.",
    "summary": "Customer on the Pro plan asking to update billing details."
  }
}
```

### `helpdesk_ticket.opened`

| `data.ticket` field | Description |
| --- | --- |
| `id` | The ticket's ID. |
| `case_reference` | The reference shown in the Helpdesk, such as `HD-0E9NEGBM`. |
| `agent_id` | The agent the ticket came from. |
| `conversation_id` | The conversation it belongs to. |
| `channel` | `embed`, `public_link`, or `whatsapp`. |
| `status` | `new` for a new ticket. |
| `requester_name` | The customer's name, if known. |
| `subject` | The ticket's subject. |
| `summary` | A short summary of the request. |
| `created_at` | When it was opened. |

## Delivery and retries

- **Success**: your endpoint must answer with any `2xx` status within **10 seconds**. Do slow work, like calling other APIs, after you respond.
- **Retries**: any other status, a timeout, or a connection error is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, and 6 hours. After 6 attempts the delivery is marked **Failed**.
- **Redirects** aren't followed. Use your endpoint's final URL.
- **Duplicates**: the same event can arrive more than once, for example if your server timed out after processing it. Use the payload `id` (or the `webhook-id` header) to skip events you've already handled.
- **Order**: events aren't guaranteed to arrive in order. Use `created_at` if order matters.

### Delivery history

Select **Deliveries** on a webhook to see its 10 most recent deliveries: the event, whether it was **Delivered**, is **Retrying**, or **Failed**, the HTTP status your endpoint returned, and any error. Delivery history is kept for 30 days.

## Manage webhooks

From the webhook's **…** menu you can:

- **Edit** the URL, agent, and events, or turn the webhook off without deleting it.
- **Send test event** to check your endpoint. Turn a webhook on before testing it.
- **Rotate signing secret** if the secret may have leaked. The old secret stops working immediately, so update your endpoint right away.
- **Delete** the webhook.

Changes to webhooks are recorded in your team's activity log.

## Requirements

- Your endpoint must use **HTTPS** and resolve to a **public** IP address. Addresses on private networks, such as `localhost` or `192.168.x.x`, are rejected. To test locally, use a tunnel such as ngrok or Cloudflare Tunnel.
- Webhooks stop sending if your team's plan drops below Standard or billing is suspended.
