AI Chatbot Engine · Docs
Webhooks
Get leads, conversations and sync results in your CRM, helpdesk or spreadsheet as they happen. Add an endpoint in the dashboard under Settings → Webhooks (admins), choose its events, and send a test event to check it. Zapier’s and Make’s “catch hook” URLs work as endpoints.
Events
lead.created: A visitor left their details (form or typed in chat).conversation.started: A visitor’s first message on your website or through the API.conversation.ended: No messages for 30 minutes; includes a short summary.source.sync_completed: A website or file source finished re-reading, with what changed.source.sync_failed: A source couldn’t be read; includes the reason.message.feedback: A visitor gave an answer a thumbs up or down, with their reason if any.handoff.requested: A visitor asked for a person: their details and question, or the link they opened, with the conversation.webhook.test: sent when you click “Send test event”.
What we send
POST /your/endpoint
Content-Type: application/json
X-Webhook-Event: lead.created
X-Webhook-Id: 0192f6c4-5b1e-7c3a-9d2f-6a8b7c1d2e3f
X-Signature: t=1790000000,v1=5f2c…
{
"id": "0192f6c4-5b1e-7c3a-9d2f-6a8b7c1d2e3f",
"type": "lead.created",
"created_at": "2026-10-02T14:12:03.512Z",
"org_id": "…",
"agent_id": "…",
"data": {
"lead_id": "…",
"conversation_id": "…",
"fields": { "name": "Jane", "email": "jane@example.com" },
"consented": true,
"via": "form"
}
}- Answer with any 2xx status within 10 seconds. Anything else is retried with growing gaps for about 24 hours.
- An endpoint that fails for 3 days in a row is switched off, and your admins get an email. Switch it back on and resend from the delivery log.
- A resent delivery keeps the same
id, so you can ignore one you have already handled. - Endpoints must be
https://on the public internet.
Zapier and Make
No code needed: a webhook can start a Zap or a Make scenario, and from there reach thousands of apps.
- Zapier: create a Zap with the trigger Webhooks by Zapier → Catch Hook and copy the URL it shows. Make: add the module Webhooks → Custom webhook, create a webhook and copy its address.
- In the dashboard, Settings → Webhooks → Add a webhook: paste the address and choose the events, for example “Lead captured”.
- Click “Send test event”, then in Zapier “Test trigger” (or in Make “Redetermine data structure”), so it learns the fields.
- Add the next steps: a row in Google Sheets, a deal in your CRM, a message to your team.
Zapier and Make don’t check the signature; that’s fine for these addresses, which are secret. Treat them like passwords.
For Slack and HubSpot there are built-in integrations under Settings → Integrations.
Verify the signature
X-Signature is t=<unix time>,v1=<hex HMAC-SHA256>, computed with your endpoint’s signing secret over the timestamp, a dot and the raw body. Recompute it, compare in constant time, and reject timestamps older than a few minutes.
Node.js
import crypto from 'node:crypto';
// rawBody: the request body exactly as received (a string), before JSON parsing.
export function verifyWebhook(secret, rawBody, header, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // too old: maybe a replay
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const given = String(parts.v1 || '');
return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given));
}Python
import hashlib
import hmac
import time
def verify_webhook(secret: str, raw_body: bytes, header: str, tolerance: int = 300) -> bool:
"""raw_body: the request body exactly as received, before JSON parsing."""
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > tolerance: # too old: maybe a replay
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Test vector
Your verification should accept exactly this (with the timestamp check relaxed):
secret whsec_test_0123456789abcdefghijklmnopqrstu
timestamp 1790000000
body {"id":"evt_1","type":"lead.created"}
header t=1790000000,v1=62e917a576423ea0c58161adf53198b861c1b5753c50305fddaaabe4bc198754