Real-time event notifications
Webhooks let your application react to email events the instant they occur. Mailpipe sends an HTTP POST to your endpoint every time a subscribed event fires — no polling required.
Register one or more HTTPS endpoints in your account settings or via the API. Each endpoint can subscribe to any combination of event types. When an event fires, Mailpipe builds a JSON payload and POST it to every matching endpoint within milliseconds.
Events dispatched within milliseconds of occurrence
Every request is signed so you can verify authenticity
Failed deliveries retried with exponential backoff
Subscribe to individual events or use * to receive all events. Unused event types are simply not delivered to your endpoint.
email.receivedA new inbound email has arrived and been stored in a mailbox
email.sentAn outbound message has been accepted by the recipient's mail server
email.bouncedAn outbound message could not be delivered (hard or soft bounce)
email.openedThe recipient opened the email (requires open tracking to be enabled)
domain.verifiedAll DNS records for a domain have been successfully verified
All webhook deliveries share a common envelope. The data object varies by event type — it contains the full resource that changed.
{
"id": "evt_a1b2c3d4",
"type": "email.received",
"created_at": "2025-01-15T14:22:00Z",
"data": {
// varies by event type — see examples below
}
}Example payload for an email.received event:
{
"id": "evt_a1b2c3d4",
"type": "email.received",
"created_at": "2025-01-15T14:22:00Z",
"data": {
"message_id": "msg_abc123",
"mailbox_id": "mbx_xyz789",
"mailbox_email": "support@yourdomain.com",
"from": "customer@example.com",
"to": ["support@yourdomain.com"],
"subject": "Help with my order",
"snippet": "Hi, I placed an order yesterday and...",
"has_attachments": false,
"received_at": "2025-01-15T14:21:59Z"
}
}Example payload for an email.bounced event:
{
"id": "evt_b9c8d7e6",
"type": "email.bounced",
"created_at": "2025-01-15T15:00:10Z",
"data": {
"message_id": "msg_sent001",
"recipient": "nobody@invalid-domain.tld",
"bounce_type": "hard",
"bounce_code": "550",
"bounce_message": "5.1.1 The email account that you tried to reach does not exist."
}
}Every request includes a X-Mailpipe-Signature header containing an HMAC-SHA256 digest of the raw request body, signed with your webhook secret. Always verify this signature before processing the payload.
const crypto = require('crypto');
function verifyWebhookSignature(req, webhookSecret) {
const signature = req.headers['x-mailpipe-signature'];
if (!signature) return false;
// Compute expected signature from raw body
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(req.rawBody) // use the raw unparsed body buffer
.digest('hex');
// Use timingSafeEqual to prevent timing attacks
const sigBuffer = Buffer.from(signature, 'hex');
const expBuffer = Buffer.from(expected, 'hex');
return (
sigBuffer.length === expBuffer.length &&
crypto.timingSafeEqual(sigBuffer, expBuffer)
);
}
// Express example
app.post('/webhooks/mailpipe', (req, res) => {
if (!verifyWebhookSignature(req, process.env.MAILPIPE_WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = req.body;
console.log('Received event:', event.type);
res.status(200).json({ received: true });
});import hmac
import hashlib
def verify_webhook_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# Flask example
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/webhooks/mailpipe', methods=['POST'])
def webhook():
signature = request.headers.get('X-Mailpipe-Signature', '')
if not verify_webhook_signature(request.data, signature, WEBHOOK_SECRET):
abort(401)
event = request.json
print('Received event:', event['type'])
return {'received': True}, 200Use the raw body for verification
Parse the JSON only after verifying the signature. If you parse first and re-serialize, key ordering or whitespace differences will cause the signature to fail.
A delivery attempt is considered successful when your endpoint returns a 2xx HTTP status within 10 seconds. Any other response (or a timeout) triggers the retry schedule below.
| Attempt | Delay after previous attempt | Cumulative wait |
|---|---|---|
| 1st | Immediate | 0 s |
| 2nd | 30 seconds | 30 s |
| 3rd | 5 minutes | 5 min 30 s |
| 4th (final) | 30 minutes | 35 min 30 s |
If all retries are exhausted the event is marked as failed. You can view failed deliveries in the dashboard and replay them manually at any time.
Design your webhook handler to be idempotent. Because of retries (and rare network duplicates), the same event may be delivered more than once. Use the id field in the envelope to deduplicate.
Visit webhook.site to get a unique public URL you can use as your endpoint. Every request is logged and displayed in the browser so you can inspect the full payload.
curl -X POST "https://api.mailpipe.dev/v1/webhooks" \
-H "Authorization: Bearer mp_live_your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://webhook.site/your-unique-uuid",
"events": ["email.received", "email.bounced"],
"secret": "test-secret-change-me-in-production"
}'Run your handler locally and expose it via ngrok. This is ideal for iterative development because you can set breakpoints and inspect requests in your editor.
# Install ngrok, then run your local server (e.g. port 3000)
ngrok http 3000
# ngrok will print a public URL like:
# Forwarding https://abc123.ngrok-free.app -> http://localhost:3000
# Register that URL with Mailpipe:
curl -X POST "https://api.mailpipe.dev/v1/webhooks" \
-H "Authorization: Bearer mp_live_your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://abc123.ngrok-free.app/webhooks/mailpipe",
"events": ["*"],
"secret": "local-dev-secret"
}'Send a test event to any registered endpoint without waiting for a real email to arrive.
curl -X POST "https://api.mailpipe.dev/v1/webhooks/wh_abc123/test" \
-H "Authorization: Bearer mp_live_your-api-key-here" \
-H "Content-Type: application/json" \
-d '{ "event_type": "email.received" }'/v1/webhooksList all registered webhook endpoints/v1/webhooksRegister a new webhook endpoint/v1/webhooks/:idRetrieve endpoint details and delivery stats/v1/webhooks/:idUpdate URL, events, or secret/v1/webhooks/:idRemove a webhook endpoint/v1/webhooks/:id/testSend a test event to the endpoint/v1/webhooks/:id/deliveriesList delivery attempts and their status/v1/webhooks/deliveries/:id/replayReplay a failed delivery