Webhooks
Email Webhooks
Receive email lifecycle and engagement events (delivered, opened, clicked, bounced, complained, unsubscribed, and more) — registration, the signed payload envelope, HMAC-SHA256 verification, and retries.
Email webhooks push delivery and engagement events to an HTTPS endpoint you control. They use the same envelope, signing scheme, and retry behaviour as SMS webhooks — only the event names differ.
Events
| Event | Fired when |
|---|---|
email.queued |
The message is accepted and queued. |
email.sent |
The message is handed to the mail relay. |
email.delivered |
The message is delivered to the inbox. |
email.opened |
The recipient opened the message. |
email.clicked |
The recipient clicked a tracked link. |
email.bounced |
The message bounced. |
email.complained |
The recipient marked it as spam. |
email.unsubscribed |
The recipient unsubscribed. |
email.failed |
The message could not be sent. |
Register an endpoint
Add and manage endpoints from the Email app → Webhooks page. Provide a
URL, an optional description, and the events to subscribe to. WRNexus
returns a whsec_-prefixed signing secret once on creation — copy and store
it. You can pause, edit, delete, and send a test event to verify wiring.
Payload
Deliveries are POST requests, Content-Type: application/json, with the
canonical envelope:
{
"id": "whd_8f1c0e2b-...",
"event": "email.opened",
"createdAt": "2026-06-18T14:32:10Z",
"data": {
"messageId": "f58a2939-2c4b-4f0a-9b7e-1f2a3b4c5d6e",
"recipient": "customer@example.com",
"url": null
}
}
For email.clicked, data.url is the clicked link; for email.bounced /
email.complained, data.reason carries the reason. id is a unique
delivery id, reused across re-deliveries for idempotent dedupe.
Verify the signature
Every delivery includes an HMAC of the raw body in X-WRNexus-Signature:
X-WRNexus-Signature: sha256=<hex-hmac>
It is HMAC-SHA256 keyed by your endpoint’s whsec_ secret. Recompute over
the raw body and compare in constant time — the verification code is identical
to the SMS webhook example.
const crypto = require('crypto');
const expected =
'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const ok =
expected.length === header.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
Responding & retries
- Acknowledge with any
2xxwithin a few seconds. - Retries up to 5 attempts with exponential back-off (≈ 1s, 2s, 4s, 8s, capped at 60s) on any non-2xx or transport error.
- Idempotency — dedupe on the stable delivery
id.
See also
- Email API — send mail and read the delivery report.
- SMS Webhooks — the same envelope and signature scheme.
- Authentication — API keys for the send side.