Register an endpoint to receive delivery-status events from your workspace, so you can react to bounces, complaints, opens, and clicks in real time.
When an event fires, PristineSend POSTs the signed payload to each enabled endpoint subscribed to that event type. Failed deliveries are retried with exponential backoff; an endpoint that keeps failing is automatically disabled (re-enable it under Settings → Webhooks). The Events API exposes the same delivery history if you prefer to pull it.
Webhooks are HTTP POST requests sent to a URL you configure. They are triggered by delivery events from your PristineSend workspace. To set up a webhook endpoint, go to Settings → Webhooks in your dashboard and enter your endpoint URL.
PristineSend pushes these six event types. They are the same set you can filter on when reading the delivery log via the Events endpoint.
| Event type | Description |
|---|---|
email.delivered | The receiving mail server confirmed delivery. |
email.bounced | The email could not be delivered (hard or soft bounce). |
email.complained | Recipient marked the email as spam. |
email.failed | The send failed before delivery (e.g. the provider rejected the message). |
email.opened | Recipient opened the email (requires open tracking). |
email.clicked | Recipient clicked a tracked link (requires click tracking). |
All events share a common envelope: a unique id (prefixed evt_, stable across retries), the type, a created_at timestamp, and a data object. data is the same email object returned by GET /v1/emails/:id (it is null for the rare event with no associated message):
Every delivery is an HTTP POST with a JSON body and these headers:
| Header | Value |
|---|---|
X-PristineSend-Signature | t=<unix>,v1=<hex> — the timestamp and signature (see below). |
X-PristineSend-Event-Id | The event id (same as id in the body). Stable across retries — use it to dedupe. |
X-PristineSend-Event-Type | The event type, e.g. email.delivered. |
The v1 signature is the hex HMAC-SHA256 of the string `${t}.${rawBody}` — the timestamp, a literal dot, and the exact raw request body — keyed by your endpoint's signing secret used verbatim: the entire whsec_… string, prefix included, is the HMAC key — don't strip the whsec_ prefix or base64-decode it. To verify a request:
X-PristineSend-Signature header and split out t and v1.HMAC_SHA256(secret, `${t}.${rawBody}`) over the raw bytes you received — verify before JSON.parse, since re-serializing changes the bytes.v1 with a constant-time check.t is more than a few minutes old (replay protection).Your endpoint's signing secret is shown when you create it and is retrievable any time from Settings → Webhooks. A worked example is below.
A minimal Next.js App Router webhook handler that verifies the signature and dispatches on event type: