Webhooks
Receive conversation, message, lead and handoff events at your own endpoint, with retries, a delivery log, replay and test deliveries.
Last updated:
Webhooks let SiteTrellis tell your own systems when something happens in a chat: a new conversation, a captured lead, a request for a human. SiteTrellis sends an HTTPS POST with a JSON body to an address you control. Use them to push leads into a CRM, alert a team chat channel, create support tickets, or keep your own analytics.
Set up an endpoint#
- Build a public HTTPS endpoint that accepts
POSTrequests with a JSON body and answers with a2xxstatus quickly. Private, loopback and link-local addresses cannot be used as targets, and redirects are not followed. - In the dashboard open Webhooks and add an endpoint. Enter the URL, an optional description, which events to receive (or all), and whether it applies to one site or every site in the account.
- SiteTrellis generates a signing secret (it begins with
whsec_) and shows it once. Copy it into your server's configuration. Afterwards only a short hint is visible. - Click Send test event. Your endpoint receives a
webhook.testevent so you can verify the connection and the signature check.
Events#
| Event | Sent when |
|---|---|
conversation.started | A visitor starts a new conversation. |
conversation.ended | A conversation ends: the visitor, the AI or an operator closes it, or it times out. |
message.created | A message is added: visitor, assistant or operator. High volume; subscribe only if you need it. |
lead.captured | A lead is saved from a pre-chat form, the capture_lead tool or an offline form. See Lead capture. |
handoff.requested | An escalation rule or the agent asks for a human. See Escalation and live takeover. |
tool.failed | A custom HTTP tool call failed or timed out. See Custom HTTP tools. |
You can subscribe to * (everything), to specific events, or to a family with a wildcard such as conversation.*. The test button sends the extra event webhook.test; handle unknown event names by ignoring them, since new events may be added later.
Request format#
Every delivery is a POST with Content-Type: application/json and these headers:
| Header | Value |
|---|---|
X-SiteTrellis-Signature | t=<unix timestamp>,v1=<hex HMAC>. See Verify webhook signatures. |
X-SiteTrellis-Event | The event name, for example lead.captured. |
X-SiteTrellis-Delivery | The unique event id (the same value as id in the body). |
User-Agent | SiteTrellis/1.0 |
The body always has the same envelope; only data varies by event:
{
"id": "evt_9f2c1a7d4b6e8f0a1c3d",
"event": "lead.captured",
"created_at": "2026-10-07T14:03:22+00:00",
"site": { "id": "site_a1b2c3d4", "name": "Example Shop" },
"data": {
"lead_id": 318,
"conversation_id": "conv_5e6f7a8b",
"fields": {
"name": "Priya Shah",
"email": "[email protected]",
"company": "Northwind"
}
}
}
The envelope fields (id, event, created_at, site, data) are stable. The contents of data depend on the event and may gain new fields over time; write your receiver to ignore fields it does not recognise. For example, lead.captured carries lead_id, conversation_id and the captured fields; handoff.requested carries conversation_id, the action taken (takeover, email, both or message), a human-readable reason, the rule that fired, and whether it happened outside_hours. Inspect a real delivery in the delivery log to see the exact fields for each event.
Playground tests in the dashboard never send webhook events, so use the live widget (or Send test event) to try your receiver.
Responding to a delivery#
- Return any
2xxstatus as soon as you have stored or queued the event. Do the slow work afterwards. SiteTrellis waits up to 10 seconds, and accepts a response of any size but only keeps a short excerpt. - Any other result (non-2xx, timeout, connection error) is treated as a failure.
- Redirects (
301,302and so on) are not followed and count as a failure. Give the final URL.
Retries and backoff#
A failed delivery is retried automatically with increasing delays: about 30 seconds, 2 minutes, 10 minutes, 30 minutes and 2 hours after the previous attempt, for up to 6 attempts in total. After that the delivery is marked failed.
Some failures are not retried because trying again cannot help: when your endpoint answers with a client error such as 400, 401, 403, 404 or 410. (408, 425 and 429 are retried.) If you deactivate or delete the endpoint, pending deliveries to it fail.
Retries are driven by a scheduled job that runs about once a minute, so actual delays can be up to a minute longer than the figures above.
id. Store the id values you have processed and skip duplicates. Do not rely on events arriving in order.Delivery log and redelivery#
Each endpoint has a delivery log showing the event, status (pending, delivered, failed), number of attempts, last HTTP status, a short response excerpt, the duration and the time. Use it to debug your receiver. Press Redeliver on a delivery to send it again; the retry counter restarts. You can also pause an endpoint with Pause and resume it later, and use Rotate signing secret to generate a new secret (the old one stops working immediately and the new one is shown once, so copy it into your server configuration straight away). Delivery log entries are kept for 30 days and then removed.
Example receiver#
// Node.js + Express. Verify the signature first (see "Verify webhook signatures").
const express = require('express');
const app = express();
// Keep the RAW body: the signature is computed over the exact bytes received.
app.post('/hooks/sitetrellis', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifySiteTrellisSignature(req.get('X-SiteTrellis-Signature'), req.body, process.env.ST_WEBHOOK_SECRET)) {
return res.status(400).send('bad signature');
}
const event = JSON.parse(req.body.toString('utf8'));
if (alreadyProcessed(event.id)) return res.status(200).send('duplicate');
switch (event.event) {
case 'lead.captured':
queueCrmSync(event.data); // do slow work asynchronously
break;
case 'handoff.requested':
notifyTeamChat(event.site.name, event.data);
break;
default:
break; // ignore events you do not handle
}
res.status(200).send('ok');
});
Security checklist#
- Always verify the signature and reject old timestamps. Anyone can send a POST to a public URL.
- Serve the endpoint over HTTPS and keep the signing secret in server configuration, not in client code or a repository.
- Treat the contents as untrusted text. Chat messages originate from anonymous visitors, so escape them before showing in HTML and use parameterised queries when storing them.
- Use a separate endpoint (and secret) per system, so you can disable one without affecting the others.
- Monitor the delivery log. A rising failure count usually means your endpoint is down or rejecting requests.