Docs

Webhooks

Engage pushes signed events to your HTTPS endpoint as things happen — inbound messages, delivery statuses, campaign progress and more.

GET/v1/webhooks/event-types
POST/v1/webhooks— { url, description, events } → { endpoint, secret }
GET/v1/webhooks/{id}/deliveries
POST/v1/webhooks/{id}/test
POST/v1/webhooks/deliveries/{id}/replay

Setting 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

EventSent when
message.receivedA customer sends you a message (any type, including button/list replies)
message.statusAn outbound message is sent, delivered, read or fails
conversation.createdA new conversation opens
conversation.updatedStatus, assignee or window changes
contact.createdA new contact is created
contact.updatedContact fields, tags or opt-in change
campaign.progressPeriodic stats while a campaign runs
campaign.completedA campaign finishes or is cancelled
template.statusMeta approves, rejects, pauses or disables a template
number.qualityA number's quality rating or messaging limit changes

The live list for your workspace is available from GET /v1/webhooks/event-types.

Payload

POST https://your-app.example.com/webhooks/engage
{
  "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

HeaderValue
Engage-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>
Engage-Event-IdSame as the payload id — use it to deduplicate
Engage-Event-TypeThe event type
Content-Typeapplication/json

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.

Example header
Engage-Signature: t=1762857727,v1=5f3c8a2e9b0d4f6a1c7e2b8d3f9a0c4e6b1d7f2a8c3e9b5d0f4a6c2e8b1d7f3a
import 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
});
Verify against the raw body
Frameworks that parse JSON before your handler runs will change whitespace and key order. Capture the raw request bytes for verification.

Delivery, retries and replay

  • Respond with any 2xx within 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.