Webhooks

Webhooks push delivery and engagement events to your server the moment they happen — no polling.

Create an endpoint

Dashboard → Settings → Webhooks. Give us an HTTPS URL and pick the events you care about:

FieldTypeDescription
email.senteventHanded off to the receiving infrastructure.
email.deliveredeventRecipient's mail server accepted the message.
email.bouncedeventRejected — payload includes the bounce type.
email.complainedeventRecipient marked the message as spam.
email.openedeventFirst open (requires open tracking on the domain).
email.clickedeventA tracked link was clicked — payload includes the URL.
email.scheduledeventAccepted with a future scheduled_at.
email.canceledeventA scheduled email was canceled before sending.
email.suppressedeventOne or more recipients were skipped — payload lists the suppressed addresses.
email.delivery_delayedeventTemporary delivery problem; retries continue — payload includes the delay type.
email.failedeventThe email could not be sent — payload includes the error.
email.receivedeventInbound mail arrived on one of your domains — see the Receiving guide.
contact.createdeventA contact was added to an audience.
contact.updatedeventA contact changed — currently fired when someone unsubscribes.
contact.deletedeventA contact was removed from an audience.
domain.createdeventA sending domain was added.
domain.verifiedeventA domain passed ownership + DKIM verification.
domain.deletedeventA sending domain was removed.

contact.* and domain.* payloads carry the resource fields (contact_id/domain_id, etc.) instead of the email fields shown below.

Payload

POST to your URL
{
  "type": "email.delivered",
  "created_at": "2026-07-27T18:04:11.128Z",
  "data": {
    "email_id": "eml_...",
    "from": "Acme <hello@acme.com>",
    "to": ["customer@example.com"],
    "subject": "Your receipt",
    ...event-specific fields (e.g. "url" on clicks, "bounce_type" on bounces)
  }
}

Deliveries retry up to 3 times with backoff; any 2xx response counts as received. Recent deliveries — with response codes and attempt counts — are listed per-endpoint in the dashboard so you can debug your receiver.

Verify signatures

Every delivery is signed with the endpoint's secret (whsec_..., shown in the dashboard). Two headers accompany each request:

sendibl-timestamp: 1753639451
sendibl-signature: hex(HMAC_SHA256(secret, timestamp + "." + raw_body))
Node — verify
import crypto from "crypto";

function verify(req, secret) {
  const ts = req.headers["sendibl-timestamp"];
  const expected = crypto.createHmac("sha256", secret)
    .update(`${ts}.${req.rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(req.headers["sendibl-signature"] ?? ""),
  );
}
Compute the HMAC over the raw request body, before any JSON parsing — re-serialized JSON won't match. Reject stale timestamps (e.g. older than 5 minutes) to prevent replay.

Pause or remove

Toggle an endpoint off to stop deliveries without losing its configuration; delete it to remove it permanently.