Webhooks
Subscribe an HTTPS endpoint to one or more events. Sendix posts a JSON payload signed with HMAC-SHA256 and retries on the standard backoff curve.
Event catalog
21 events across three resource families. Subscribe to as many as you need on each endpoint.
| Event | Resource | When it fires |
|---|---|---|
| email.scheduled | Future-dated send registered in the outbound queue. | |
| email.sent | Queued and routed to the delivery worker. | |
| email.accepted_by_remote_mx | Recipient MX accepted the message during direct delivery. | |
| email.delivered | Accepted by the recipient MX; inbox placement is not implied. | |
| email.delivery_delayed | Temporary 4xx — will retry on the standard backoff curve. | |
| email.opened | Tracking pixel loaded (deduplicated per recipient). | |
| email.clicked | Tracked link was clicked (deduplicated per link). | |
| email.bounced | Permanent 5xx; recipient automatically suppressed. | |
| email.complained | ARF / FBL spam complaint received. | |
| email.unsubscribed | Recipient opted out and was added to suppression controls. | |
| email.failed | Internal failure before handoff (no MX attempt). | |
| email.suppressed | Send blocked by an active suppression list entry. | |
| email.received | Inbound message landed on a configured inbox. | |
| domain.created | domain | Sending domain added to the workspace. |
| domain.updated | domain | Domain settings or DNS records changed. |
| domain.dkim_rotation_started | domain | A new DKIM selector was generated and is waiting for DNS. |
| domain.verified | domain | SPF / DKIM / DMARC all green for the first time. |
| domain.deleted | domain | Domain removed from the workspace. |
| contact.created | contact | New contact added to any audience. |
| contact.updated | contact | Contact attributes or subscription state changed. |
| contact.deleted | contact | Contact removed (GDPR-compliant hard delete). |
Signature verification
Every delivery carries X-Sendix-Signature in the format t=<unix>,v1=<sha256>. Compute HMAC_SHA256(secret, t + "." + raw_body) and compare the v1 digest in constant time. Reject any request wheret is more than five minutes old — that's the replay window.
import { verifyWebhookSignature } from 'sendix-node';
export async function verifySendix(req) {
const signature = req.headers['x-sendix-signature'];
const rawBody = req.rawBody;
const ok = await verifyWebhookSignature(
rawBody,
signature,
process.env.SENDIX_WEBHOOK_SECRET,
);
if (!ok) {
throw new Error('invalid signature');
}
}X-Sendix-Signature, then parse JSON. Many frameworks mutate whitespace or encoding when body parsers run first.Operate webhooks
Treat every webhook delivery as operational evidence. Your handler should be fast, idempotent, and easy to inspect when a customer asks what happened.
- Persist the event idStore the id before doing slow work so retries can be deduplicated safely.
- Reject unsigned trafficRequests without a valid signature should return 401 and avoid side effects.
- Respond quicklyEnqueue long work and return 204. Slow handlers create unnecessary retries.
- Monitor failuresTrack endpoint error rate, retry count, and replay activity in your own logs.
Retry policy
What we do when your endpoint doesn't answer with a 2xx.
- Up to 24 hExponential backoff with jitter, capped at 1 hour between attempts.
- 2xx = successAny 2xx ends the retry loop. 3xx is treated as failure (no auto-follow).
- SSRF-safeWe refuse private CIDRs, localhost, and IMDS endpoints at registration.
- Replay anytimeFailed and DLQ delivery rows can be replayed from the dashboard while retained.
Return quickly from your webhook handler. For slow work, persist the event id and enqueue your own job, then respond with 204. If a retry arrives with the same event id, treat it as idempotent.

