SupportAiDocs

Webhooks

Events, payloads, signatures and retries.

Webhooks push events from your workspace to any HTTPS endpoint. Create them under Settings → Webhooks, choose which events to receive, and use the signing secret to verify requests.

Events #

EventFires when
conversation.createdA new conversation starts on any channel.
conversation.closedA conversation is marked closed by a person or by the AI.
message.createdA visitor, AI or agent message is saved (internal notes excluded).
lead.createdA lead is captured via form, email capture or the capture_lead tool.
escalation.createdAn escalation rule fires or the agent hands off to a human.
action.failedA custom action returned a non-2xx or timed out.

Payload #

json
{
  "id": "evt_5f1c…",
  "type": "lead.created",
  "createdAt": "2026-09-16T10:12:03.000Z",
  "data": {
    "chatbot": {
      "uuid": "0b0e…",
      "name": "Acme Support"
    },
    "lead": {
      "id": 88,
      "conversationId": 4021,
      "name": "Sam",
      "email": "sam@example.com",
      "phone": null,
      "fields": {
        "budget": "£5k"
      },
      "source": "form"
    }
  }
}

Headers on every delivery:

HeaderValue
X-SupportAi-EventThe event type.
X-SupportAi-DeliveryUnique delivery id — use it for idempotency.
X-SupportAi-Signaturet=<unix seconds>,v1=<hex hmac>

Verifying signatures #

Compute HMAC-SHA256 over `${t}.${rawBody}` with your endpoint secret and compare it to v1 in constant time. Reject timestamps older than five minutes to prevent replays.

js
import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"));
}
Use the raw body
Verify against the exact bytes received, before any JSON parsing or re-serialisation. In Express use express.raw() for the webhook route.

Retries #

We expect a 2xx within 10 seconds. Anything else is retried with exponential backoff (30s, 1m, 2m, 4m, 8m, capped at 6h) for up to six attempts, then marked failed. You can inspect every delivery, see the response code, and replay from the dashboard.