Guides

Webhooks

Parse provider delivery events into EmailEvent or DeliveryEvent shapes, with optional signature checks.

Use webhooks when a provider posts delivery updates (delivered, bounced, failed, and similar). Email parsers return EmailEvent[]. SMS and WhatsApp parsers return DeliveryEvent[]. Map email events with toDeliveryEvent when you want one ingest pipeline.

The one rule

Verify the signature against the unmodified raw request body before you trust or act on the event.

Quick start

Import one provider subpath

Prefer sently/webhooks/<provider> so runtimes without a bundler do not load every parser.

import { parse, verifySignature } from "sently/webhooks/sndr";

Verify, then parse

const rawBody = await request.text(); // exact bytes as a string
const signature = request.headers.get("x-sndr-signature") ?? "";

const ok = await verifySignature(
  rawBody,
  signature,
  process.env.SNDR_WEBHOOK_SECRET!,
);
if (!ok) {
  return new Response("bad signature", { status: 400 });
}

const events = parse(JSON.parse(rawBody));

Handle normalized events

for (const event of events) {
  if (event.type === "bounced" || event.type === "failed") {
    // suppress or alert using event.messageId / event.recipient
  }
}

Provider entrypoints

ImportChannelParse exportVerify export
sently/webhooks/brevoEmailparseEmailEvent[]
sently/webhooks/mailgunEmailparseverifySignature, verifyPayload
sently/webhooks/postmarkEmailparse
sently/webhooks/resendEmailparseverifySignature
sently/webhooks/sendgridEmailparse
sently/webhooks/sesEmailparse
sently/webhooks/sndrEmailparseverifySignature
sently/webhooks/twilio-smsSMSparseDeliveryEvent[]verifySignature (X-Twilio-Signature)
sently/webhooks/unifonicSMSparseDeliveryEvent[]
sently/webhooks/whatsapp-cloudWhatsAppparseDeliveryEvent[]verifySignature (X-Hub-Signature-256)

The convenience barrel sently/webhooks re-exports named helpers such as parseSndrWebhook, parseTwilioSmsWebhook, and toDeliveryEvent. Bundlers tree-shake unused names; Node/Deno without a bundler evaluate every re-export.

SMS status callbacks

Twilio posts form fields (MessageSid, MessageStatus, To). Pass a parsed object or URLSearchParams:

import { parse } from "sently/webhooks/twilio-sms";

const events = parse(await request.formData().then((fd) => Object.fromEntries(fd)));

SNDR signatures

SNDR signs with X-Sndr-Signature: t=<unix>,v1=<hex>. The digest is HMAC-SHA256 over `${t}.${rawBody}`.

Consequence: Re-serializing JSON changes whitespace and key order and breaks verification — always use the raw body string.

Default timestamp tolerance is 300 seconds. Pass { toleranceSeconds: 0 } to disable the freshness check.

Troubleshooting

Learn more

Next

On this page