Endpoints

Webhooks

Register HTTP endpoints that receive real-time email events.

Webhooks POST a JSON event to your HTTPS endpoint when an email’s status changes. Respond with a 2xx within 10 seconds; other responses are retried, up to 12 attempts in total. Every request is signed with HMAC-SHA256 so you can verify it came from SNDR.

Signature verification

Each request carries X-Sndr-Signature: t=<unix seconds>,v1=<hex>. v1 is the HMAC-SHA256 of "<t>.<raw body>", keyed with the webhook secret. While a staged key exists, a second signature v1s is added; accept the request if any signature matches.

JavaScript
const crypto = require('crypto')

// header  = req.headers['x-sndr-signature']
// rawBody = raw request body as a string, before JSON.parse
function verifySignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > toleranceSec) return false
  const expected = crypto.createHmac('sha256', secret)
    .update(parts.t + '.' + rawBody)
    .digest('hex')
  return Object.entries(parts).some(([key, sig]) =>
    key.startsWith('v1') &&
    sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))
}

Event payload

JSON
{
  "id":         "evt_5c1e9a7b-2d4",
  "type":       "email.delivered",
  "created_at": "2026-06-01T12:00:04Z",
  "data": {
    "email_id": "em_3f9c1a2b-7d4",
    "to":       ["customer@example.com"]
  }
}

email.bounced and email.failed events carry data.email_id and data.error instead of data.to.

Supported events

  • email.delivered — the receiving mail server accepted the email.
  • email.bounced — the recipient address was rejected as invalid (hard bounce).
  • email.failed — delivery failed for another reason.
  • email.unsubscribed — a recipient unsubscribed using the link in an email.
  • email.queued and email.complained — accepted when subscribing, but not currently sent.