Email webhooks you can verify, retry and replay.
Know when an email is delivered, bounces or draws a complaint, with events your endpoint can trust and does not silently miss.
- No card needed to test
- Prices in euros, excluding VAT
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(request, rawBody, secret) {
const id = request.headers["outstamp-id"];
const timestamp = request.headers["outstamp-timestamp"];
const header = request.headers["outstamp-signature"] ?? "";
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${id}.${timestamp}.${rawBody}`)
.digest("hex");
return header.split(",")
.map((part) => part.trim().replace(/^v1=/, ""))
.some((candidate) => candidate.length === expected.length &&
timingSafeEqual(Buffer.from(candidate, "hex"), Buffer.from(expected, "hex")));
}What every delivery carries.
A signature over the raw body
Outstamp-Signature: v1=is an HMAC-SHA256 of the delivery id, the timestamp and the exact body. Reject anything older than 5 minutes.Retries for 48 hours
10 attempts: at once, then after 5 seconds, 1, 5 and 30 minutes, and 2, 6, 12, 24 and 48 hours.
Replay for 30 days
Replay any event from the dashboard or the API. It gets a new delivery id and keeps its event id.
Rotation without downtime
When you rotate the secret, both the old and the new signature are sent for 24 hours.
Events you can subscribe to.
| Group | Events | Typical use |
|---|---|---|
| Delivery | email.sent, email.delivered, email.delivery_delayed | Mark a message as delivered in your app |
| Problems | email.bounced, email.complained, email.rejected, email.failed, email.suppressed | Stop emailing an address, or alert support |
| Lifecycle | email.accepted, email.scheduled, email.queued, email.canceled, email.submission_unknown | Audit trails and scheduled sends |
| Domains | domain.created, domain.status_changed, domain.deleted | Know when DNS breaks |
| Project | suppression.created, suppression.deleted, project.quota_warning, project.reputation_warning, project.sending_paused, project.sending_resumed | Operational alerts |
Plan for these before you build.
They are the rules of any honest webhook system.
- Delivery is at least once: deduplicate on the event
id. - Order is not guaranteed:
email.deliveredcan arrive beforeemail.sent. - Answer with any
2xxwithin 10 seconds, and do the work afterwards. - After 20 failures in a row the endpoint is disabled, and a
410answer disables it at once.
Receive your first event.
- 1
Add an endpoint
A public HTTPS URL and the event types you want. The signing secret is shown once.
- 2
Verify the signature
Use the raw body, compare in constant time and reject old timestamps.
- 3
Trigger a test event
Send to
[email protected]with a test key, or use the test button in the dashboard.
Questions about webhooks.
Are webhooks signed?
Yes. Every delivery carries Outstamp-Signature: v1=, an HMAC-SHA256 of the delivery id, the timestamp and the raw body.
What if my endpoint is down?
We retry 10 times over 48 hours. After 20 failures in a row the endpoint is disabled, and you can replay what it missed for 30 days.
Do test emails trigger webhooks?
Yes. Test sends produce the same events and webhooks as live ones, marked as test.
Receive your first signed event today.
Test keys and the simulator are free, with no card.
- No card needed to test
- Prices in euros, excluding VAT