Skip to content
DocsWebhooks

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.

EventResourceWhen it fires
email.scheduledemailFuture-dated send registered in the outbound queue.
email.sentemailQueued and routed to the delivery worker.
email.accepted_by_remote_mxemailRecipient MX accepted the message during direct delivery.
email.deliveredemailAccepted by the recipient MX; inbox placement is not implied.
email.delivery_delayedemailTemporary 4xx — will retry on the standard backoff curve.
email.openedemailTracking pixel loaded (deduplicated per recipient).
email.clickedemailTracked link was clicked (deduplicated per link).
email.bouncedemailPermanent 5xx; recipient automatically suppressed.
email.complainedemailARF / FBL spam complaint received.
email.unsubscribedemailRecipient opted out and was added to suppression controls.
email.failedemailInternal failure before handoff (no MX attempt).
email.suppressedemailSend blocked by an active suppression list entry.
email.receivedemailInbound message landed on a configured inbox.
domain.createddomainSending domain added to the workspace.
domain.updateddomainDomain settings or DNS records changed.
domain.dkim_rotation_starteddomainA new DKIM selector was generated and is waiting for DNS.
domain.verifieddomainSPF / DKIM / DMARC all green for the first time.
domain.deleteddomainDomain removed from the workspace.
contact.createdcontactNew contact added to any audience.
contact.updatedcontactContact attributes or subscription state changed.
contact.deletedcontactContact 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');
  }
}
Verify before parsing
Read the raw request body, verify 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.
Use webhooks with delivery reasons
Delivery events include the status and the reason context available at that point in the pipeline. Use those fields instead of parsing provider-specific SMTP text.

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.