Webhooks
Receive account events at your URL and verify their signatures.
View as Markdown ↗How webhooks work
Webhooks push account events to your server the moment they happen — no polling. Email Bump sends a signed POST with a JSON body and retries with backoff until your endpoint returns a 2xx. Every attempt is logged so you can debug delivery.
A webhook URL has to resolve to a public address. Anything pointing at localhost, a private range, or a cloud metadata address is refused when you save it and again when a delivery is attempted — a name that resolves somewhere private later doesn't get a second chance. Redirects aren't followed, so the URL you give us is the only place we send.
Subscribe to events
- 01Add an endpoint
Go to Developers → Webhooks and add your HTTPS URL.
- 02Pick events
Subscribe to specific events, or leave them all unchecked to receive everything.
- 03Save the signing secret
It's shown once on creation — store it to verify incoming requests.
- 04Send a test
Use Send test event and watch it appear under the endpoint's deliveries.
Payload shape
Every delivery is a JSON envelope with the event type and its data:
{
"id": "dfe336e3-9730-4eb4-8841-7b0bbd766dda",
"type": "contact.created",
"created": "2026-07-21T19:56:38Z",
"data": {
"contact": {
"id": "5fef0867-b292-46ca-8371-e24d8575576c",
"email": "[email protected]",
"subscribed": true
}
}
}Two headers accompany every request:
Email-Bump-Event: contact.created
Email-Bump-Signature: t=1784663798,v1=acf8fe8e05a9fde3347ad708ac3640c9…Event types
contact.created,contact.updated,contact.deletedcontact.subscribed,contact.unsubscribedemail.delivered,email.opened,email.clickedemail.bounced,email.complainedemail.received— inbound mail arrivingcampaign.sent
That's the complete list — subscribing to none means you receive all of them.
Verify signatures
The Email-Bump-Signature header is t=<timestamp>,v1=<hmac>, where the HMAC is a SHA-256 of {timestamp}.{raw_body} keyed with your endpoint secret. Recompute it and compare before trusting a request:
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("="))
);
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1)
);
}Verify against the exact bytes you received, before any JSON re-serialization — re-encoding can change the body and break the signature.