Docs
Webhooks
Engage pushes signed events to your HTTPS endpoint as things happen — inbound messages, delivery statuses, campaign progress and more.
/v1/webhooks/event-types/v1/webhooks— { url, description, events } → { endpoint, secret }/v1/webhooks/{id}/deliveries/v1/webhooks/{id}/test/v1/webhooks/deliveries/{id}/replaySetting up an endpoint
Add endpoints in Developers → Webhooks or via the API. Choose the events to receive. The signing secret is shown once when the endpoint is created — store it securely. Endpoints must use HTTPS and resolve to a public address.
Event types
The live list for your workspace is available from GET /v1/webhooks/event-types.
Payload
{
"id": "evt_01JCQ6Z8X3V7K9M2N4P6R8T0W2",
"type": "message.status",
"created_at": "2026-11-11T10:42:07Z",
"org_id": "4d2a…",
"data": {
"message": {
"id": "0f9b1c7e-5a43-4c1d-9d0e-6f2a8b3c4d51",
"conversation_id": "a81c…",
"direction": "out",
"type": "template",
"status": "delivered",
"client_ref": "order-48213",
"error": null,
"delivered_at": "2026-11-11T10:42:07Z"
}
}
}Headers
Verifying signatures
Each request is signed with your endpoint secret. Compute HMAC_SHA256(secret, t + "." + raw_body) as lowercase hex and compare it with v1 using a constant-time comparison. Reject requests whose timestamp is more than five minutes away from your clock to prevent replay attacks.
Engage-Signature: t=1762857727,v1=5f3c8a2e9b0d4f6a1c7e2b8d3f9a0c4e6b1d7f2a8c3e9b5d0f4a6c2e8b1d7f3aimport crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.ENGAGE_WEBHOOK_SECRET; // whsec_…
const TOLERANCE_SECONDS = 300;
// Use the RAW body — re-serialised JSON will not match the signature.
app.post("/webhooks/engage", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("Engage-Signature") ?? "";
const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
const t = Number(parts.t);
const v1 = parts.v1 ?? "";
if (!t || Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) {
return res.status(400).send("stale or missing timestamp");
}
const expected = crypto
.createHmac("sha256", SECRET)
.update(`${t}.${req.body.toString("utf8")}`)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(v1, "hex");
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(400).send("invalid signature");
}
const event = JSON.parse(req.body.toString("utf8"));
// Deduplicate on event.id — deliveries are at-least-once.
queueForProcessing(event);
res.sendStatus(200); // respond fast; do heavy work asynchronously
});Delivery, retries and replay
- Respond with any
2xxwithin 10 seconds to acknowledge. Do slow work asynchronously. - Failed deliveries (non-2xx, timeouts, connection errors) are retried with exponential backoff for up to 24 hours.
- Delivery is at-least-once and events may arrive out of order — deduplicate on the event id and compare timestamps.
- Every attempt is logged with its response code and body under Developers → Webhooks → Deliveries, where you can replay any delivery.
- Endpoints that fail continuously are disabled automatically and admins are emailed. Re-enable them from the dashboard.
Testing
Send a sample event to an endpoint with POST /v1/webhooks/{id}/test or the Send test button in the dashboard.