A recipient reported an email as spam
Fired on a spam complaint. The recipient is unsubscribed and suppressed.
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
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.
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.
The event name, e.g. email.delivered. Equal to the body's event.
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.
Unix time of this delivery attempt in SECONDS, as a decimal string. Note the unit differs from X-Sendly-Timestamp.
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.
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
The email.complained delivery body.
Response Body
Example Requests
The email.complained delivery body.
/email.complained