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:
| Field | Type | Description |
|---|---|---|
| email.sent | event | Handed off to the receiving infrastructure. |
| email.delivered | event | Recipient's mail server accepted the message. |
| email.bounced | event | Rejected — payload includes the bounce type. |
| email.complained | event | Recipient marked the message as spam. |
| email.opened | event | First open (requires open tracking on the domain). |
| email.clicked | event | A tracked link was clicked — payload includes the URL. |
| email.scheduled | event | Accepted with a future scheduled_at. |
| email.canceled | event | A scheduled email was canceled before sending. |
| email.suppressed | event | One or more recipients were skipped — payload lists the suppressed addresses. |
| email.delivery_delayed | event | Temporary delivery problem; retries continue — payload includes the delay type. |
| email.failed | event | The email could not be sent — payload includes the error. |
| email.received | event | Inbound mail arrived on one of your domains — see the Receiving guide. |
| contact.created | event | A contact was added to an audience. |
| contact.updated | event | A contact changed — currently fired when someone unsubscribes. |
| contact.deleted | event | A contact was removed from an audience. |
| domain.created | event | A sending domain was added. |
| domain.verified | event | A domain passed ownership + DKIM verification. |
| domain.deleted | event | A 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
{
"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))
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"] ?? ""),
);
}Pause or remove
Toggle an endpoint off to stop deliveries without losing its configuration; delete it to remove it permanently.