Log in

Webhooks

Webhooks connect a bot to the rest of your tools. Chat Thing can call your URL when something happens, such as a new conversation or a customer asking for a person, and your systems can call a Chat Thing URL to re-sync a data source, for example when you publish an article in your CMS.

Webhooks are set up per bot, on the bot's Webhooks tab. You need to be an owner or admin of the bot's team.

Webhook types

WebhookDirectionFires when
Sync triggerIncoming: your system calls Chat ThingYour system calls the webhook's URL. It starts a sync on one data source
Sync successOutgoing: Chat Thing calls your URLA data source finishes syncing
Sync failureOutgoingA data source fails to sync
Conversation startedOutgoingA new conversation starts with the bot
Conversation escalatedOutgoingA user asks to speak to a person through the Talk to a human power-up
Conversation claimedOutgoingA team member takes over a conversation
Conversation releasedOutgoingA team member hands a conversation back to the bot

Incoming webhooks only need a secret, which is part of the URL you give to the other system. Outgoing webhooks need the URL to send events to, plus a secret you can use to check the request really came from Chat Thing.

Add a webhook

  1. Open your bot and go to the Webhooks tab.
  2. Click New webhook.
  3. Choose the webhook type. For a Sync trigger webhook, also choose the Data source it should sync.
  4. Click Create webhook. The webhook's settings page opens. New webhooks start turned off.
  5. Fill in the settings (see Outgoing webhook settings or Sync trigger webhook) and click Save.
  6. Test it (see Test a webhook).
  7. Turn on the toggle at the top of the settings page. It changes to Webhook is enabled.

Webhooks tab with no webhooks yet and a New webhook button

New webhook page listing each webhook type as a selectable card

Choosing the Sync trigger webhook opens a Choose a data source window listing the bot's data sources

Manage webhooks

  • Turn a webhook on or off with the toggle on its card or at the top of its settings page. A turned-off outgoing webhook sends no events (though you can still test it). A turned-off Sync trigger webhook rejects calls to its URL.
  • Edit a webhook: on its card, open the menu (⋮) and choose Settings.
  • Delete a webhook: open the menu (⋮), choose Delete and confirm. This can't be undone, and a Sync trigger webhook's URL stops working.

Webhook card with its menu open, showing Settings and Delete

The confirmation window asking "Are you sure you want to delete this webhook?" with Cancel and Delete buttons

Test a webhook

  • Outgoing webhooks: click Test hook at the bottom of the settings page. Chat Thing sends a sample event to your saved URL, using your saved headers and body, and shows the response status and body at the top of the page. It uses the saved settings, so click Save first. Testing works even while the webhook is turned off, and test requests carry the header X-ChatThing-Test: true.
  • Sync trigger webhooks: click Trigger webhook. It opens the webhook URL in a new tab, which starts a sync.

Test result panel showing a 200 OK response at the top of the webhook settings page

Outgoing webhook settings

Delivery

  • Webhook target URL: the full URL to send events to, including https://. Chat Thing sends a POST unless you change the method in a custom payload.
  • Data source (Sync success and Sync failure only): leave empty to fire for every data source on the bot, or choose one.

Authentication

Every outgoing webhook has a Hook secret, used to sign each request (see Verify requests). Click the regenerate icon to create a new one. Once you save a new secret, requests are signed with it, so update your receiver at the same time.

Use a preset

To post events straight into a chat app, click Slack or Discord under Use a preset, then paste that app's incoming webhook URL as the Webhook target URL. The preset fills in a ready-made message you can edit. Click Clear preset to go back.

Custom payload

By default, each webhook sends a fixed JSON body (shown for each type below) with a POST. Turn on Custom payload to change it:

  • HTTP method: POST, PUT, PATCH, GET or DELETE.
  • Request headers: a JSON object of headers.
  • Request body: the exact text to send.

You can put variables in the target URL, headers and body using {{path}} placeholders. They're filled in from the event when it's sent. For example, on a Conversation started webhook:

{ "bot": "{{conversation.botName}}", "message": "{{conversation.initialMessage}}" }

Useful details:

  • Most variables also have a short name, such as {{botName}}, {{conversationId}}, {{initialMessage}}, {{userData}} or {{agentName}}. The variables sidebar shows both.
  • Add | json to insert a value safely inside a JSON string, for example "text": "New chat: {{initialMessage | json}}". It escapes quotes and line breaks, and turns objects into text.
  • The target URL and headers can only use single values (text, numbers or true/false), such as {{conversation.id}}, not whole objects such as {{conversation}}. The body can use both: objects and arrays are sent as JSON.
  • If a placeholder doesn't match anything (for example a typo like {{conversation.userData.emial}}), it's sent as-is. Test hook warns you about these before you go live.

Custom payload section with header and body editors and the variables sidebar

Variables sidebar

The sidebar lists every variable for the webhook type. Click a variable to insert it into the field you last clicked. While you're editing the URL or headers, it only shows single values. While you're editing the body, it shows everything, and marks objects with an object badge. Open Variable reference at the bottom for a description of each one.

Sync trigger webhook

Call this webhook's URL from another system to re-sync a data source, for example from your CMS when you publish a page.

  1. Choose the Data source to sync.
  2. Save, turn the webhook on, and copy the URL from the sidebar. It looks like this:
https://app.chatthing.ai/api/public/hooks/{hookId}/{secret}

The secret is part of the URL, so there's no separate header. Regenerating the secret changes the URL and the old one stops working.

🚨

Treat the URL like a password

Anyone with the URL can start a sync on your data source. If it leaks, regenerate the secret.

Call it with any HTTP method:

curl --request POST \
  --url https://app.chatthing.ai/api/public/hooks/{hookId}/{secret}
const response = await fetch(
  "https://app.chatthing.ai/api/public/hooks/{hookId}/{secret}",
  { method: "POST" },
);
console.log(await response.json());
import requests

response = requests.post("https://app.chatthing.ai/api/public/hooks/{hookId}/{secret}")
print(response.json())

A successful call returns { "success": true } once the sync has started. If a sync is already running, or your team is out of storage tokens, you get 429 with "success": false and a message explaining why. Each sync uses storage tokens.

Event payloads

Every outgoing event body includes a deliveryId: the same id as the X-ChatThing-Delivery-Id header. It stays the same across retries, so you can use it to ignore duplicates.

The conversation events also include conversation.conversationLink, a link that opens the conversation in Chat Thing.

Sync success webhook

Fires when a data source on the bot finishes syncing. If you chose a data source, it only fires for that one.

FieldTypeDescription
successbooleanAlways true
results.totalTokensnumberStorage tokens used by this sync
results.modifiedRowsnumberRows that changed since the last sync
results.unmodifiedRowsnumberRows that didn't change
results.totalDataSourceRowsnumberRows in the data source
results.totalDocumentsnumberDocuments created from the data source
botstringThe bot's name
dataSourcestringThe data source's name
{
  "success": true,
  "deliveryId": "d3c5a9e8-1b2c-4f5a-9b8d-1a2b3c4d5e6f",
  "results": {
    "totalTokens": 241245,
    "modifiedRows": 15,
    "totalDocuments": 20,
    "unmodifiedRows": 0,
    "totalDataSourceRows": 15
  },
  "bot": "Support bot",
  "dataSource": "Help centre"
}

Sync failure webhook

Fires when a data source on the bot fails to sync. If you chose a data source, it only fires for that one.

FieldTypeDescription
successbooleanAlways false
reasonstringWhy the sync failed
botstringThe bot's name
dataSourcestringThe data source's name
{
  "success": false,
  "deliveryId": "d3c5a9e8-1b2c-4f5a-9b8d-1a2b3c4d5e6f",
  "reason": "Over plan storage token limit, please upgrade plan",
  "bot": "Support bot",
  "dataSource": "Help centre"
}

Conversation started webhook

Fires when a new conversation starts with the bot, on any channel.

FieldTypeDescription
eventstringconversation.started
timestampstringWhen the event was sent (ISO 8601)
conversation.idstringThe conversation's id
conversation.botIdstringThe bot's id
conversation.botNamestringThe bot's name
conversation.channelTypestringThe channel, such as web or slack
conversation.conversationLinkstringLink to the conversation in Chat Thing
conversation.createdAtstringWhen the conversation started (ISO 8601)
conversation.initialMessagestringThe user's first message, when there is one
conversation.userDataobjectAnything known about the user, such as details from the pre-chat form or identifyUser. {} when nothing is known
{
  "event": "conversation.started",
  "deliveryId": "d3c5a9e8-1b2c-4f5a-9b8d-1a2b3c4d5e6f",
  "timestamp": "2026-03-10T12:00:00.000Z",
  "conversation": {
    "id": "abc-123",
    "botId": "def-456",
    "botName": "Support bot",
    "channelType": "web",
    "conversationLink": "https://app.chatthing.ai/app/conversations/bot/def-456?conversation=abc-123",
    "createdAt": "2026-03-10T12:00:00.000Z",
    "initialMessage": "What are your opening hours?",
    "userData": { "name": "Alex Doe", "email": "alex@example.com" }
  }
}

Conversation escalated webhook

Fires when a user asks to speak to a person through the Talk to a human power-up, on any channel, whether or not you use human takeover.

FieldTypeDescription
eventstringconversation.escalated
timestampstringWhen the event was sent (ISO 8601)
conversation.id, botId, botName, channelType, conversationLinkAs for Conversation started
conversation.statestringEscalated
conversation.userDataobjectAnything known about the user. {} when nothing is known
userEmailstringThe email address the user gave
{
  "event": "conversation.escalated",
  "deliveryId": "d3c5a9e8-1b2c-4f5a-9b8d-1a2b3c4d5e6f",
  "timestamp": "2026-03-10T12:05:00.000Z",
  "conversation": {
    "id": "abc-123",
    "botId": "def-456",
    "botName": "Support bot",
    "channelType": "web",
    "conversationLink": "https://app.chatthing.ai/app/conversations/bot/def-456?conversation=abc-123",
    "state": "Escalated",
    "userData": {}
  },
  "userEmail": "user@example.com"
}

Conversation claimed webhook

Fires when a team member takes over a conversation. See Human takeover.

FieldTypeDescription
eventstringconversation.claimed
timestampstringWhen the event was sent (ISO 8601)
conversation.id, botId, botName, channelType, conversationLinkAs for Conversation started
conversation.statestringActive
agent.namestringThe team member's name
agent.emailstringThe team member's email address
{
  "event": "conversation.claimed",
  "deliveryId": "d3c5a9e8-1b2c-4f5a-9b8d-1a2b3c4d5e6f",
  "timestamp": "2026-03-10T12:10:00.000Z",
  "conversation": {
    "id": "abc-123",
    "botId": "def-456",
    "botName": "Support bot",
    "channelType": "web",
    "conversationLink": "https://app.chatthing.ai/app/conversations/bot/def-456?conversation=abc-123",
    "state": "Active"
  },
  "agent": {
    "name": "Sam",
    "email": "sam@example.com"
  }
}

Conversation released webhook

Fires when a team member hands a conversation back to the bot.

FieldTypeDescription
eventstringconversation.released
timestampstringWhen the event was sent (ISO 8601)
conversation.id, botId, botName, channelType, conversationLinkAs for Conversation started
conversation.statestringResolved
agent.namestringThe team member's name
agent.emailstringThe team member's email address
{
  "event": "conversation.released",
  "deliveryId": "d3c5a9e8-1b2c-4f5a-9b8d-1a2b3c4d5e6f",
  "timestamp": "2026-03-10T12:15:00.000Z",
  "conversation": {
    "id": "abc-123",
    "botId": "def-456",
    "botName": "Support bot",
    "channelType": "web",
    "conversationLink": "https://app.chatthing.ai/app/conversations/bot/def-456?conversation=abc-123",
    "state": "Resolved"
  },
  "agent": {
    "name": "Sam",
    "email": "sam@example.com"
  }
}

Verify requests

Every outgoing request is signed with your hook secret. Checking the signature proves the request came from Chat Thing and wasn't changed on the way. If you're only trying things out (for example with webhook.site), you can skip this.

Request headers

HeaderDescription
X-ChatThing-Signaturesha256=<hex>: an HMAC-SHA256 of the exact request body, using your hook secret as the key
X-ChatThing-Delivery-IdAn id that stays the same across retries of one delivery. Use it to ignore duplicates
X-ChatThing-EventThe event name: sync.success, sync.failure, conversation.started, conversation.escalated, conversation.claimed or conversation.released
X-ChatThing-Testtrue when the request was sent with Test hook. Not sent for real events
X-Secret-KeyYour hook secret, kept for older integrations. Prefer checking the signature

Check the signature

Calculate an HMAC-SHA256 of the raw request body with your hook secret, hex-encode it, add sha256= in front, and compare it with X-ChatThing-Signature using a constant-time comparison.

import crypto from "node:crypto";

function verifyChatThingSignature(rawBody, headerValue, secret) {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(headerValue ?? "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
import hmac
import hashlib

def verify_chatthing_signature(raw_body: bytes, header_value: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header_value or "")

Use the raw body exactly as received. Parsing it as JSON and turning it back into text changes the bytes, and the signature won't match.

Retries and timeouts

  • If your receiver fails or is unreachable, Chat Thing tries again, up to 5 attempts in total, waiting longer each time (about 30 seconds, then 1, 2 and 4 minutes).
  • Retries keep the same X-ChatThing-Delivery-Id, so receivers that check it won't process an event twice.
  • Each request times out after 10 seconds.
  • Redirects (3xx responses) aren't followed. Use the final URL.
  • URLs that point to private or internal network addresses are blocked.

Troubleshooting

My webhook isn't firing

Check the webhook is turned on: the top of its settings page should say Webhook is enabled. Turned-off outgoing webhooks don't send real events, though Test hook still works. For Sync success and Sync failure, check the Data source setting isn't limited to a different source.

My receiver gets {{conversation.id}} as text

The placeholder didn't match anything, usually because of a typo. Insert variables from the sidebar instead of typing them, and check the Test hook result for an unresolved placeholders warning.

My signature check always fails

Make sure you're using the raw request body, not a parsed and re-serialised copy, and the current Hook secret. If you regenerated the secret, update your receiver.

I just want to see what a webhook sends

webhook.site gives you a free, temporary URL that shows every request it receives. Use it as the Webhook target URL, click Test hook, and you'll see exactly what your server would get.

Need an event that isn't listed here? Email support@chatthing.ai.

  • Take over a live chat from your bot

    Let your team step into a live website chat, reply to the customer directly while the bot pauses, then hand the conversation back to the bot.

  • Hand off a chat to a human

    Let your bot email your team with the visitor's email and the conversation when someone asks for a person, and mark the chat as escalated.

  • Keep your bot's knowledge up to date

    Re-sync data sources by hand, on a daily, weekly or monthly schedule, or automatically when you publish in your CMS, and see what each sync changes.

Last updated