SendlySendly
API ReferenceWebhooks

An email failed terminally

Fired when a send is dead: refused or failed by Sendly, rejected by SES, or unable to render. A send that is merely waiting on a retry announces nothing.

Sendly POSTs this JSON to the URL you registered, signed two ways (see the headers). Verify the signature over the RAW body bytes before parsing; re-serialising the JSON changes the bytes. Slack incoming-webhook URLs receive a Slack message body instead of this one, with the same headers.

Answer with any 2xx within the request timeout to acknowledge. Any other outcome is a failed attempt: a 4xx is treated as permanent and is not retried, anything else (5xx, timeout, connection error) is retried up to 9 attempts in total, waiting about 30 s, 2 min, 10 min, 30 min, 2 h, 6 h, 12 h and 24 h between them (each with ±20% jitter). A webhook is disabled after 20 consecutive failed attempts, retries included, so one event that exhausts its 9 attempts counts 9 toward that; any successful delivery resets the count. Every retry of an event reuses its webhook-id, so dedupe on it.

Header Parameters

X-Sendly-Signature*string

Legacy signature: lowercase hex HMAC-SHA256 of <X-Sendly-Timestamp>.<raw request body>, keyed by the secret STRING (UTF-8, whsec_ prefix included, NOT base64-decoded). Carries the CURRENT secret only, so it has no rotation overlap; prefer webhook-signature.

X-Sendly-Timestamp*string

Unix time of this delivery attempt in MILLISECONDS, as a decimal string. It is the first half of the legacy signed string and is the same value as timestamp in the body.

X-Sendly-Event*string

The event name, e.g. email.delivered. Equal to the body's event.

webhook-id*string

Standard Webhooks message id: unique per (webhook, event) pair and stable across every retry of that delivery, so it is the key to dedupe on (a retry is a new HTTP request carrying the same webhook-id). Two endpoints subscribed to the same event receive different ids.

webhook-timestamp*string

Unix time of this delivery attempt in SECONDS, as a decimal string. Note the unit differs from X-Sendly-Timestamp.

webhook-signature*string

Standard Webhooks signature list: one or more space-separated entries of the form v1,<base64>, where <base64> is the HMAC-SHA256 of <webhook-id>.<webhook-timestamp>.<raw request body>. For a secret that starts with whsec_ the HMAC key is the BASE64-DECODED remainder; any other secret keys the HMAC with its UTF-8 bytes. For 24 hours after a secret rotation the list carries TWO entries (new secret first, then the previous one): accept the delivery if ANY entry matches.

Content-Type*string
User-Agent*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

The email.failed delivery body.

Response Body

Example Requests

The email.failed delivery body.

POST/email.failed