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

> Receive messages, leads, and escalations at your own endpoint as they happen.

Webhooks POST conversation events from SiteGPT to a URL you control. They
are available on plans and add-ons that include webhook support; if
**Settings → Webhooks** is not available on your account, check the
[Billing](/docs/navigating-your-account/billing) page. There
are three independent lanes, each with its own URL and token, configured in
**Settings → Webhooks**:

| Lane            | Fires on                                  | Event names                                                     |
| --------------- | ----------------------------------------- | --------------------------------------------------------------- |
| **Messages**    | Every message exchanged                   | `ADD_MESSAGE`                                                   |
| **Leads**       | Every captured lead                       | `NEW_LEAD_CAPTURE`                                              |
| **Escalations** | A conversation escalates to human support | `CONVERSATION_ESCALATED`, then `CONVERSATION_ESCALATED_UPDATED` |

## Setup

<Steps>
  <Step title="Create an endpoint">
    Any HTTPS URL that accepts a JSON POST works: your backend, a serverless
    function, or an automation platform's inbound webhook.
  </Step>

  <Step title="Configure the lane">
    In **Settings → Webhooks**, fill in the URL for the lane you need
    (**Messages Webhook URL**, **Leads Webhook URL**, or
    **Escalation Webhook URL**) and set a token for it. The token can be any
    secret string you choose.
  </Step>

  <Step title="Verify the token on your side">
    Every delivery includes your token in the `X-WEBHOOK-TOKEN` header.
    Reject requests whose token does not match. This is the only
    authentication on deliveries, so treat the token as a secret.
  </Step>
</Steps>

<Note>
  Deliveries are sent once, with no automatic retries. Respond quickly with a
  2xx and process the payload asynchronously on your side. If you need
  guaranteed processing, make your handler idempotent and reconcile misses
  through the [Agent API](/docs/api-reference/v2/getting-started).
</Note>

## Payloads

Every payload has the same envelope: `{ "event": "...", "data": { ... } }`.

### Messages

```json theme={null}
{
  "event": "ADD_MESSAGE",
  "data": {
    "chatbotId": "your-chatbot-id",
    "threadId": "conversation-id",
    "gptModel": "gpt-4",
    "sources": ["https://example.com/docs/page"],
    "question": "What are your support hours?",
    "answer": "Our team is available 9 to 5 CET on weekdays.",
    "messageType": "NORMAL_MESSAGE",
    "agentId": null,
    "iconUrl": null,
    "agentName": null,
    "systemMessageType": null
  }
}
```

* The lane fires for every message, so `question` and `answer` are each
  nullable: a visitor message arrives with `answer: null`, and bot, agent,
  and system messages can carry `question: null`. Never model both fields
  as required.
* `gptModel` carries the internal model id: `gpt-3.5-turbo` corresponds to
  the **GPT-4.1 Mini** setting and `gpt-4` to **GPT-4.1**.
* Human-agent replies include `agentId`, `agentName`, and `iconUrl`.
* System events in the conversation arrive as `messageType:
  "SYSTEM_MESSAGE"` with the specific kind in `systemMessageType`.

### Leads

```json theme={null}
{
  "event": "NEW_LEAD_CAPTURE",
  "data": {
    "chatbotId": "your-chatbot-id",
    "chatbotName": "Acme Support",
    "dashboardUrl": "https://sitegpt.ai/your-chatbot-id/leads/lead-id",
    "leadDetails": {
      "id": "lead-id",
      "name": "Ada Lovelace",
      "email": "ada@example.com",
      "phone": "+1 555 0100",
      "customData": { "company_size": "11-50" },
      "capturedAt": "2026-07-17T09:30:00.000Z"
    }
  }
}
```

`customData` contains your [custom lead fields](/docs/features/lead-collection)
keyed by field name.

### Escalations

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

One escalation fires exactly one `CONVERSATION_ESCALATED`. If the visitor
submits their contact details afterwards, a separate
`CONVERSATION_ESCALATED_UPDATED` event follows with the same shape and a
populated `user`. Create your ticket on `CONVERSATION_ESCALATED` and enrich
it on the update, keyed by `threadId`; do not treat the second event as a
new escalation.

## Example handler

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

  // Acknowledge fast; process async.
  res.status(200).end()

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

## Testing

Point a lane at a request inspector (your own staging endpoint or any
HTTP-bin style tool you trust), then trigger the event: send a message in
the widget preview, submit the lead form, or escalate a conversation. You
will see the exact payloads your production endpoint will receive.
