Skip to content

Webhooks

Webhooks deliver real-time notifications to your server when email events occur — delivery, bounces, complaints, opens, and clicks.


Setup

  1. Go to Dashboard → Webhooks
  2. Click Register Webhook
  3. Enter your HTTPS endpoint URL
  4. Select the event types you want to receive
  5. Click Register

Or use the Webhooks API.


Event types

EventTrigger
deliveredEmail successfully delivered to recipient’s mail server
soft_bouncedTemporary delivery failure (e.g. mailbox full)
hard_bouncedPermanent delivery failure (e.g. address doesn’t exist)
complainedRecipient marked the email as spam
openedRecipient opened the email (requires open tracking)
clickedRecipient clicked a tracked link
unsubscribedRecipient clicked the unsubscribe link
deferredDelivery delayed, will retry

Payload format

{
"event_type": "delivered",
"timestamp": "2026-04-20T14:30:00Z",
"data": {
"message_id": "550e8400-e29b-41d4-a716-446655440000",
"to_email": "user@example.com",
"subject": "Your order is confirmed"
}
}

See Webhook Events Reference for the full payload for each event type.


Signature verification

Every webhook request includes an X-Emitlo-Signature header. Always verify it before processing the event.

X-Emitlo-Signature: sha256=abc123def456...
X-Emitlo-Event: delivered
X-Emitlo-Delivery-Id: 7f3a9b2c-...

The signature is HMAC-SHA256(raw_request_body, webhook_secret) encoded as hex.

import crypto from 'crypto';
import express from 'express';
const app = express();
app.post('/webhooks/emitlo', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-emitlo-signature'];
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Event:', event.event_type, event.data.message_id);
res.status(200).send('OK');
});

Retry behavior

If your endpoint returns a non-2xx response or times out, Emitlo retries with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry8 hours

After 5 failed attempts, the delivery is marked as failed. You can view delivery logs in the dashboard.


Best practices

  • Respond quickly — return 200 OK immediately, then process the event asynchronously (queue it)
  • Be idempotent — the same event may be delivered more than once; use X-Emitlo-Delivery-Id to deduplicate
  • Handle all event types — even if you only care about delivered, handle unknown types gracefully
  • Use HTTPS — webhook endpoints must use HTTPS