Skip to content

Documentation

Build with Engage

Connect customer data, business events, and communication channels to build automated journeys with workflows and AI.

Webhooks

Instead of asking Engage what changed, let Engage tell you: a signed POST to your system the moment a customer writes, a message is read, or a sale is marked.

Owners only

Adding, changing and removing webhooks needs the owner role - an endpoint receives your customers' messages. Set them up in Settings → Webhooks, or through the API.

Add an endpoint

POST /v1/webhooks

{
  "url": "https://crm.example.com/engage-events",
  "description": "Our CRM",
  "events": ["message.received", "sale.created"]
}

The address must be https on the public internet - a host name, not an IP address. The answer includes secret, shown this once: keep it in your receiver to check signatures. Up to 10 endpoints per workspace.

FieldTypeNotes
message.receivedeventA customer sent a message. data: conversationId, customerId, from, type, text, sentAt.
message.statuseventA message you sent was delivered, read or failed. data: conversationId, providerMessageId, status, detail.
sale.createdeventA conversation was marked as a sale. data: saleId, conversationId, customerId, amount, currency, note.
customer.opted_outeventA customer said STOP. data: channel, handle, scope, reason.
campaign.completedeventA campaign finished. data: campaignId, name, sent, skipped, unknown.

What arrives

POST to your address

X-Engage-Event: message.received
X-Engage-Event-Id: 5b1f0c2e-6a4d-4c1e-9d0f-2f8a7c3b9e11
X-Engage-Delivery: 0e9d7a41-3c2b-4f6e-8a1d-5b4c3a2f1e0d
X-Engage-Signature: t=1789812000,v1=5f2b...

{
  "id": "5b1f0c2e-6a4d-4c1e-9d0f-2f8a7c3b9e11",
  "type": "message.received",
  "createdAt": "2026-09-19T10:40:00Z",
  "workspaceId": "…",
  "data": { "conversationId": "…", "from": "2348156218098", "text": "Is the blue bag still available?" }
}

Answer with any 2xx within 10 seconds. Anything else - or no answer - is tried again after 1 minute, 5 minutes, 30 minutes, 2, 6, 12 and 24 hours, then marked failed; you can retry a failed delivery from the dashboard. Redirects are not followed.

Deliveries can repeat - use the event id

Engage sends at least once: a timeout after your system has already saved the event means it arrives again. Store id (also in X-Engage-Event-Id) and ignore one you have already handled. Order is not guaranteed either - compare createdAt if it matters.

Check the signature

v1 is the HMAC-SHA256, keyed with your secret, of the timestamp, a dot, and the raw request body. Compute it over the body exactly as received - before any JSON parsing - and reject a timestamp more than five minutes old, so a captured request cannot be replayed.

Node.js

import crypto from 'node:crypto';

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  return expected.length === parts.v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Test it

POST /v1/webhooks/{id}/test - or Send a test in the dashboard - sends a ping event to that endpoint whatever it subscribed to. GET /v1/webhooks/{id}/deliveries lists the last 50, with the status code or error of each.

Next

Errors →

What every error response looks like.