Documentation
Webhooks
Subscribe to real-time events — delivery, bounces, complaints, opens and clicks — instead of polling. Create an endpoint under Developers → Webhooks.
Events
email.queued·email.sent·email.deliveredemail.delivery_delayed·email.bounced·email.rejectedemail.complained·email.failed·email.cancelled·email.suppressedemail.opened·email.clicked
Domain & contact
domain.created·domain.verified·domain.updated·domain.deletedcontact.created·contact.updated·contact.deletedwebhook.test— sent when you test an endpoint from the dashboard.
Delivery headers
Every webhook POST carries these headers:
| Header | Value |
|---|---|
X-Mailstein-Event | The event type, e.g. email.delivered. |
X-Mailstein-Call | Unique ID for this delivery attempt. |
X-Mailstein-Timestamp | Unix-ms time the request was signed. |
X-Mailstein-Signature | v1=<hmac-sha256-hex> over timestamp + body. |
X-Mailstein-Retry | true if this is a redelivery. |
Verify the signature
Compute an HMAC-SHA256 of timestamp + "." + rawBody with your endpoint's signing secret and compare, in constant time, against X-Mailstein-Signature.
Node.js
import crypto from "crypto"; function verify(rawBody, headers, secret) { const ts = headers["x-mailstein-timestamp"]; const sig = headers["x-mailstein-signature"]; const expected = "v1=" + crypto .createHmac("sha256", secret) .update(ts + "." + rawBody) .digest("hex"); return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)); }
The Node SDK ships a
Webhooks helper that does this for you. Always verify before trusting a payload, and respond 2xx quickly — non-2xx responses are retried (watch X-Mailstein-Retry to stay idempotent).