Webhooks. Hear about events as they happen.
Add an HTTPS endpoint and SimplyBoost sends it a signed request when a conversation is handed over or resolved, a lead is captured, or knowledge is ready.
On this page
Events
| Event | When | `data` contains |
|---|---|---|
conversation.escalated | A conversation is handed to your team | conversation, and escalation with reason, category and urgency |
conversation.resolved | A conversation is marked resolved | conversation |
lead.captured | A visitor leaves an email or phone number (once per lead) | lead |
knowledge.synced | A knowledge source is ready, or failed | source |
{
"id": "evt_9b2f6c1e8a4d4f7b9e3c1a5d7f2b4e6c",
"type": "conversation.escalated",
"createdAt": "2026-10-07T09:35:12+00:00",
"data": {
"conversation": { "threadId": "3f0c…", "channel": "web", "mode": "human", "escalated": true, … },
"escalation": { "reason": "The visitor was charged twice and asked for a person.", "category": "billing", "urgency": "high" }
}
}conversation, lead and source have the same fields as in the API responses. The id is the same on every retry of one event.
Verify the signature
Each request has three headers: SimplyBoost-Event (the type), SimplyBoost-Delivery (the event id) and SimplyBoost-Signature: t=<unix time>,v1=<signature>. The signature is the hex HMAC-SHA256 of <t>.<raw body> with the endpoint's secret. Check it against the raw body before parsing it, and refuse timestamps older than five minutes.
import crypto from "node:crypto";
export function verifySimplyBoost(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const timestamp = Number(parts.t);
if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const given = Buffer.from(parts.v1 || "", "hex");
return given.length === 32 && crypto.timingSafeEqual(given, Buffer.from(expected, "hex"));
}import hashlib, hmac, time
def verify_simplyboost(raw_body: bytes, header: str, secret: str, tolerance_seconds: int = 300) -> bool:
parts = dict(part.split("=", 1) for part in header.split(","))
timestamp = parts.get("t", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance_seconds:
return False
signed = f"{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Delivery and retries
- Reply with any
2xxstatus within 10 seconds. Do slow work after replying. - A failed delivery is retried up to 6 times, waiting longer each time (about half an hour in total).
- After 20 failed deliveries in a row the endpoint is switched off (
active: false, withdisabledReason). Add it again once it is fixed. - Redirects are not followed, and the address must stay public: an endpoint that starts pointing at a private network is switched off.
- An event can arrive more than once and out of order. Use its
idto skip repeats andcreatedAtto order.
Add a webhook endpoint
/webhooksWebhook endpoints belong to the account and receive events from all its chatbots.
Permission: webhooks:write
Body (JSON)
urlA public https:// address, up to 500 characters.
eventsAt least one of conversation.escalated, conversation.resolved, lead.captured, knowledge.synced.
curl -X POST https://get-api.simplyboost.io/api/v1/webhooks \
-H "Authorization: Bearer $SIMPLYBOOST_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/simplyboost", "events": ["conversation.escalated", "lead.captured"]}'Response 201 Created
{
"ok": true,
"data": {
"webhook": {
"id": "5e8a2c1d-7f3b-4a9e-b6d0-1c4f8e2a9b73",
"url": "https://example.com/hooks/simplyboost",
"events": ["conversation.escalated", "lead.captured"],
"active": true,
"disabledReason": null,
"lastDeliveryAt": null,
"lastStatusCode": null,
"createdAt": "2026-10-07T11:00:00+00:00",
"secret": "whsec_…"
}
},
"meta": { "requestId": "req_7c1e4b2a9f0d4e6c8b3a5d7f9e1c2b4a" }
}Response fields
data.webhook.idThe endpoint's ID.
data.webhook.urlWhere events are sent.
data.webhook.eventsThe events it receives.
data.webhook.activeFalse when it was switched off after repeated failures.
data.webhook.disabledReasonWhy it was switched off.
data.webhook.lastDeliveryAtWhen the last event was sent.
data.webhook.lastStatusCodeYour server's last response status.
data.webhook.createdAtWhen it was added.
data.webhook.secretThe signing secret, whsec_…. Shown only in this response: store it.
Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | url is not a public https:// address, or events is empty or has an unknown event. |
| 400 | endpoint_limit | The account already has 10 endpoints. |
List webhook endpoints
/webhooksThe account's endpoints (without their secrets) and the events you can subscribe to.
Permission: webhooks:read
curl https://get-api.simplyboost.io/api/v1/webhooks \
-H "Authorization: Bearer $SIMPLYBOOST_TOKEN"Response 200 OK
{
"ok": true,
"data": {
"webhooks": [{ "id": "5e8a…", "url": "https://example.com/hooks/simplyboost", "events": ["conversation.escalated", "lead.captured"], "active": true, … }],
"events": ["conversation.escalated", "conversation.resolved", "knowledge.synced", "lead.captured"]
},
"meta": { "requestId": "req_7c1e4b2a9f0d4e6c8b3a5d7f9e1c2b4a" }
}Response fields
data.webhooks[].idThe endpoint's ID.
data.webhooks[].urlWhere events are sent.
data.webhooks[].eventsThe events it receives.
data.webhooks[].activeFalse when it was switched off after repeated failures.
data.webhooks[].disabledReasonWhy it was switched off.
data.webhooks[].lastDeliveryAtWhen the last event was sent.
data.webhooks[].lastStatusCodeYour server's last response status.
data.webhooks[].createdAtWhen it was added.
data.eventsEvery event type you can subscribe to.
Errors
Only the common errors (see Responses and errors).
Send a test event
/webhooks/{webhookId}/testSends a signed webhook.test event to the endpoint now and reports what your server answered.
Permission: webhooks:write
Path parameters
webhookIdThe webhook endpoint, from webhook.id.
curl -X POST https://get-api.simplyboost.io/api/v1/webhooks/WEBHOOK_ID/test \
-H "Authorization: Bearer $SIMPLYBOOST_TOKEN"Response 200 OK
{
"ok": true,
"data": { "delivered": true, "statusCode": 200 },
"meta": { "requestId": "req_7c1e4b2a9f0d4e6c8b3a5d7f9e1c2b4a" }
}Response fields
data.deliveredTrue when your server answered with a 2xx.
data.statusCodeYour server's status, or null when it could not be reached.
Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No such webhook endpoint, or this token may not see it. |
| 400 | validation_error | The address now points at a private or internal network. |
Remove a webhook endpoint
/webhooks/{webhookId}Stops sending events to the endpoint.
Permission: webhooks:write
Path parameters
webhookIdThe webhook endpoint, from webhook.id.
curl -X DELETE https://get-api.simplyboost.io/api/v1/webhooks/WEBHOOK_ID \
-H "Authorization: Bearer $SIMPLYBOOST_TOKEN"Response 200 OK
{
"ok": true,
"data": { "id": "5e8a2c1d-7f3b-4a9e-b6d0-1c4f8e2a9b73", "deleted": true },
"meta": { "requestId": "req_7c1e4b2a9f0d4e6c8b3a5d7f9e1c2b4a" }
}Response fields
data.idThe removed endpoint's ID.
data.deletedAlways true.
Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No such webhook endpoint, or this token may not see it. |