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

# Webhooks

> Reference for SiteGPT webhooks: the four events, their payloads, the token header, and how deliveries work.

Webhooks send conversation events from SiteGPT to a URL that you own. SiteGPT sends a `POST` request with a JSON body each time an event happens.

## Availability

Webhooks are available on the Scale plan and above, or with the webhook add-on. See [Plans and limits](/docs/reference/plans-and-limits).

Without webhook access, the **Webhooks** tab does not show in **Settings**.

## Settings

Each chatbot has three webhook URLs, in **Settings** > **Webhooks**. Each URL has its own token. Fill in only the ones you need, then select **Save Changes**.

| Field | Events sent |
| - | - |
| **Messages Webhook URL** and **Messages Webhook Token** | `ADD_MESSAGE` |
| **Escalation Webhook URL** and **Escalation Webhook Token** | `CONVERSATION_ESCALATED`, `CONVERSATION_ESCALATED_UPDATED` |
| **Leads Webhook URL** and **Leads Webhook Token** | `NEW_LEAD_CAPTURE` |

These are the only four events. SiteGPT sends no other webhook events.

## Request format

| Part | Value |
| - | - |
| Method | `POST` |
| `Content-Type` header | `application/json` |
| `X-WEBHOOK-TOKEN` header | The token you set for that URL. Empty if you did not set one. |
| Body | `{ "event": "...", "data": { ... } }` |

## Security

* The `X-WEBHOOK-TOKEN` header is the only way to check that a request comes from SiteGPT. Set a long random token and reject requests where the header does not match.
* SiteGPT does not sign webhook requests. There is no signature header.
* Treat each token as a secret. Anyone who knows your URL and token can send you fake events.

## Delivery

* SiteGPT sends each event once. It does not retry a failed delivery.
* SiteGPT does not read your response. A `4xx` or `5xx` status is not reported anywhere.
* Answer quickly with a `2xx` status and do slow work after you respond.
* To find events that you missed, read conversations and leads with [API v2](/docs/api-reference/v2/getting-started).

## Events

### ADD\_MESSAGE

Sent for every message saved in a conversation: AI answers, visitor messages in a human-handled conversation, replies from your team, and system messages.

```json theme={null}
{
  "event": "ADD_MESSAGE",
  "data": {
    "chatbotId": "YOUR_CHATBOT_ID",
    "threadId": "YOUR_CONVERSATION_ID",
    "gptModel": "gpt-4.1",
    "sources": ["https://example.com/help/opening-hours"],
    "question": "What are your opening hours?",
    "answer": "We are open from 9:00 to 17:00, Monday to Friday.",
    "messageType": "NORMAL_MESSAGE",
    "agentId": null,
    "iconUrl": null,
    "agentName": null,
    "systemMessageType": null
  }
}
```

| Field | Description |
| - | - |
| `chatbotId` | The chatbot ID. |
| `threadId` | The conversation ID. |
| `gptModel` | The model that wrote an AI answer: `gpt-4.1` or `gpt-4.1-mini`. Can be `null`. |
| `sources` | The source URLs used for the answer. Can be an empty array. |
| `question` | The visitor's text. Can be `null`. |
| `answer` | The answer text. Can be `null`. |
| `messageType` | `NORMAL_MESSAGE` (visitor question and AI answer), `USER_MESSAGE` (visitor message), `AGENT_MESSAGE` (reply from your team), or `SYSTEM_MESSAGE`. |
| `agentId`, `agentName`, `iconUrl` | Set on replies from your team. Otherwise `null`. |
| `systemMessageType` | The kind of system message when `messageType` is `SYSTEM_MESSAGE`. Otherwise `null`. |

Do not treat `question` or `answer` as required. Each one can be `null`, depending on the message type.

### NEW\_LEAD\_CAPTURE

Sent when the chatbot collects a new lead.

```json theme={null}
{
  "event": "NEW_LEAD_CAPTURE",
  "data": {
    "chatbotId": "YOUR_CHATBOT_ID",
    "chatbotName": "Support Bot",
    "dashboardUrl": "https://sitegpt.ai/YOUR_CHATBOT_ID/leads/YOUR_LEAD_ID",
    "leadDetails": {
      "id": "YOUR_LEAD_ID",
      "name": "Alex Doe",
      "email": "visitor@example.com",
      "phone": "+1 555 0100",
      "customData": { "company_size": "11-50" },
      "capturedAt": "2026-07-17T09:30:00.000Z"
    }
  }
}
```

* `dashboardUrl` opens the lead in your dashboard.
* `customData` holds the answers to your custom lead form fields, keyed by field name. See [Collect leads](/docs/guides/leads/collect-leads).
* `capturedAt` is an ISO 8601 date and time.

### CONVERSATION\_ESCALATED

Sent when a conversation is escalated to your team. Each escalation sends this event one time. In rare cases, when SiteGPT must process an escalation again after an internal error, the same event can arrive twice. Use `threadId` to detect duplicates.

```json theme={null}
{
  "event": "CONVERSATION_ESCALATED",
  "data": {
    "chatbotId": "YOUR_CHATBOT_ID",
    "threadId": "YOUR_CONVERSATION_ID",
    "dashboardUrl": "https://sitegpt.ai/YOUR_CHATBOT_ID/chat-history/YOUR_CONVERSATION_ID",
    "user": null
  }
}
```

* `dashboardUrl` opens the conversation in your dashboard.
* `user` is `null` when SiteGPT does not know the visitor yet. When it knows the visitor, `user` has `id`, `name`, `email`, `phone`, `verified`, `createdAt`, and `updatedAt`.

### CONVERSATION\_ESCALATED\_UPDATED

Sent to the escalation URL when the visitor gives contact details after an escalation. It has the same fields as `CONVERSATION_ESCALATED`, with `user` filled in.

Create your ticket on `CONVERSATION_ESCALATED`. Update it on `CONVERSATION_ESCALATED_UPDATED`, matched by `threadId`. Do not treat the update as a new escalation.

## HIPAA mode

For chatbots in HIPAA mode, SiteGPT removes conversation content and personal data from webhook bodies:

* `ADD_MESSAGE`: `question` and `answer` are `null`, and `sources` is empty.
* Escalation events: `user` is `null`.
* `NEW_LEAD_CAPTURE`: the name, email, phone, and custom fields are empty.

See [HIPAA](/docs/account/hipaa).

## Example handler

```javascript theme={null}
// Node.js with Express
app.post('/sitegpt-webhook', express.json(), (req, res) => {
  if (req.get('X-WEBHOOK-TOKEN') !== process.env.SITEGPT_WEBHOOK_TOKEN) {
    return res.status(401).end();
  }

  // Answer first, then do the work
  res.status(200).end();

  const { event, data } = req.body;
  switch (event) {
    case 'ADD_MESSAGE':
      saveMessage(data.threadId, data.question, data.answer);
      break;
    case 'NEW_LEAD_CAPTURE':
      addToCrm(data.leadDetails);
      break;
    case 'CONVERSATION_ESCALATED':
      createTicket(data.threadId, data.dashboardUrl);
      break;
    case 'CONVERSATION_ESCALATED_UPDATED':
      addContactToTicket(data.threadId, data.user);
      break;
  }
});
```

## Test your webhook

SiteGPT has no test button. To see real payloads, point a URL at a request inspector that you trust. Then send a message in the chat preview, submit the lead form, or escalate a conversation.

## Legacy API v0

The legacy API v0 can also set a messages webhook URL and token for one conversation. That value replaces the chatbot's messages webhook for that conversation. See the [v0 reference](/docs/api-reference/getting-started).


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