Webhooks
Webhooks let your backend receive a server-to-server callback whenever specific events happen in your project — no polling required.
Creating a subscription
- Open your project in the dashboard.
- Go to Webhooks.
- Add a new subscription with:
- A destination URL (must be
http://orhttps://). - The events you want to receive.
- A destination URL (must be
- Save. Appilots starts sending a
POSTrequest to your URL whenever a subscribed event occurs.
Available events
| Event | Fires when |
|---|---|
project.created | A project is created. |
project.deleted | A project is deleted. |
conversation.escalated | A human escalation is created (a user requests a human, or the agent gives up and the user accepts the handoff). |
escalation.message | A message crosses the escalation channel, in either direction — the end-user or the operator. |
escalation.claimed | An operator claims a pending escalation (from the dashboard or via an operator API key). |
escalation.resolved | An escalation is resolved and control returns to the AI. |
This is the complete list of currently available events — more may be added over time.
Escalation event payloads
The four escalation events share a payload shape (data field of the delivery body):
| Field | Type | Present on | Description |
|---|---|---|---|
escalationId | string | all | The escalation this event belongs to. |
sessionId | string | all | The underlying agent session. |
projectId | string | all | The project the session belongs to. |
reason | string | conversation.escalated | user_requested or agent_gave_up. |
status | string | conversation.escalated, escalation.claimed, escalation.resolved | The escalation's status after this event. |
operatorName | string | escalation.claimed, escalation.message (operator replies) | Display name of the operator — a dashboard user's name, or the operatorName an operator API key attached to the claim/reply. |
message | object | escalation.message | { id, role, content, createdAt } — role is user or human_agent. |
Example escalation.claimed delivery:
{
"event": "escalation.claimed",
"workspaceId": "...",
"data": {
"escalationId": "esc_...",
"sessionId": "sess_...",
"projectId": "proj_...",
"status": "active",
"operatorName": "Zendesk Bridge"
},
"timestamp": "2026-07-18T12:00:00.000Z"
}
Example escalation.message delivery (an operator reply):
{
"event": "escalation.message",
"workspaceId": "...",
"data": {
"escalationId": "esc_...",
"sessionId": "sess_...",
"projectId": "proj_...",
"operatorName": "Zendesk Bridge",
"message": {
"id": "msg_...",
"role": "human_agent",
"content": "Hi, this is support — how can I help?",
"createdAt": "2026-07-18T12:00:01.000Z"
}
},
"timestamp": "2026-07-18T12:00:01.000Z"
}
Delivery format
Each delivery is a POST request with a JSON body shaped like this:
{
"event": "project.created",
"workspaceId": "...",
"data": {
// event-specific fields
},
"timestamp": "2026-07-18T12:00:00.000Z"
}
Two headers accompany every delivery:
| Header | Purpose |
|---|---|
X-Appilots-Event | The event name, same as the event field in the body — a convenience for routing without parsing the body first. |
X-Appilots-Signature | sha256=<hex-hmac> — an HMAC-SHA256 signature of the raw request body, described below. |
Verifying the signature
When you create a webhook subscription, Appilots generates a secret for it and shows it to you once, at creation time — copy it immediately, the same way you would an API key. There's no way to view it again after that; if you lose it, you'll need to create a new subscription.
To verify a delivery is genuinely from Appilots, recompute the HMAC-SHA256 of the raw request body using your stored secret, and compare it against the sha256= value in the X-Appilots-Signature header. Reject the delivery if they don't match.
const crypto = require('crypto');
function isValidSignature(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const received = signatureHeader.replace(/^sha256=/, '');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex')
);
}
Compute the HMAC over the exact raw bytes of the request body — if your framework parses the body into an object before you can access the raw bytes, configure it to preserve the raw payload for this check. Use a constant-time comparison (like crypto.timingSafeEqual) rather than === to avoid timing attacks.
Delivery failures
A delivery is considered failed if your endpoint doesn't respond successfully within a short timeout, or responds with a non-2xx status. Failed deliveries are logged and visible in the dashboard for that webhook, so you can check delivery history if events aren't showing up where you expect.
Respond with a 2xx status as quickly as possible, and do any slow processing asynchronously after acknowledging receipt — don't make Appilots wait on your downstream work before you respond.
Next steps
- Projects and API keys — the project a webhook subscription belongs to.
- Security best practices — handling secrets, including webhook signing secrets, safely.