SendlySendly
API ReferenceWebhooks

Contacts were created in bulk

Fired once per POST /api/contacts/bulk request instead of one contact.created per row. Imports still fire contact.created per created row.

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 contacts.bulk_created delivery body.

Response Body

Example Requests

The contacts.bulk_created delivery body.

POST/contacts.bulk_created