Webhooks
20 events,
signed and retried.
MailerSpark pushes events to your endpoints with HMAC-SHA256 signatures. Failed deliveries are retried with exponential backoff. Endpoint URLs are SSRF-scanned before they're saved.
How it works
You register a webhook endpoint in Settings → Integrations → Webhooks. We POST a JSON payload to your URL whenever a matching event happens. The request is signed with your endpoint's secret using HMAC-SHA256. You verify the signature, process the event, and return 2xx. If we don't get a 2xx, we retry with exponential backoff.
{
"id": "evt_2n4k9",
"type": "email.opened",
"createdAt": "2026-04-15T10:23:11Z",
"data": {
"campaignId": "cmp_8a3f",
"contactId": "ct_5h2k",
"email": "alex@acme.com",
"userAgent": "Mozilla/5.0...",
"ip": "203.0.113.42"
}
}Verifying the signature
Every webhook request includes three headers. Compute the HMAC of the raw request body using your endpoint's secret and compare.
X-MailerSpark-Signature: t=1700000000,v1=4f3a... X-MailerSpark-Event: email.opened X-MailerSpark-Delivery: dlv_8x2k
# Node.js example
const crypto = require('crypto');
function verify(req, secret) {
const sig = req.headers['x-mailerspark-signature'];
const [tsPart, sigPart] = sig.split(',');
const ts = tsPart.split('=')[1];
const expected = sigPart.split('=')[1];
const payload = ts + '.' + req.rawBody;
const computed = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(computed, 'hex')
);
}Event types
Subscribe to one or many. The list below is current; new events ship regularly and will appear in your dashboard changelog.
| Event | When it fires |
|---|---|
email.sent | An email was accepted by the transport (SMTP/SES) |
email.delivered | The transport confirmed delivery |
email.opened | The recipient's client loaded the tracking pixel |
email.clicked | The recipient clicked a tracked link |
email.bounced | The transport reported a hard or soft bounce |
email.complained | The recipient marked the message as spam |
email.unsubscribed | The recipient clicked the unsubscribe link |
email.replied | A cold outreach recipient replied (lands in Unibox) |
lead.status_changed | A lead transitioned between pipeline statuses |
campaign.scheduled | A campaign was scheduled for sending |
campaign.started | A campaign began sending |
campaign.completed | A campaign finished sending to all recipients |
campaign.paused | A campaign was paused |
campaign.failed | A campaign entered a failed state |
contact.created | A new contact was created |
contact.subscribed | A contact subscribed (newsletter or after opt-in) |
contact.unsubscribed | A contact unsubscribed |
sequence.step_sent | A cold outreach sequence step was sent |
sequence.completed | A sequence finished for a recipient |
inbox.health_warning | An inbox token expired or warmup is behind |
Retries
If your endpoint returns a non-2xx response, or doesn't respond within 10 seconds, we retry. The schedule is exponential backoff: 30s, 2m, 10m, 1h, 6h, 24h. After the final attempt, the delivery is marked as failed and visible in the Webhooks delivery log.
SSRF protection
When you save a webhook endpoint URL, we SSRF-scan it: no private IPs, no link-local, no localhost, no metadata endpoints. The check happens on save and on retry. If your endpoint moves, you'll get a clear error.
Growth and above
Webhooks on Growth and above.
Pair with the REST API for full read/write access to MailerSpark from your own services.