Webhooks

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.

Overview

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.

Real-time delivery

Events dispatched within milliseconds of occurrence

HMAC signatures

Every request is signed so you can verify authenticity

Automatic retries

Failed deliveries retried with exponential backoff

Event Types

Subscribe to individual events or use * to receive all events. Unused event types are simply not delivered to your endpoint.

email.received

A new inbound email has arrived and been stored in a mailbox

email.sent

An outbound message has been accepted by the recipient's mail server

email.bounced

An outbound message could not be delivered (hard or soft bounce)

email.opened

The recipient opened the email (requires open tracking to be enabled)

domain.verified

All DNS records for a domain have been successfully verified

Payload Format

All webhook deliveries share a common envelope. The data object varies by event type — it contains the full resource that changed.

Common envelope
{
  "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:

email.received payload
{
  "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:

email.bounced payload
{
  "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."
  }
}

Signature Verification

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.

Node.js — signature verification
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 });
});
Python — signature verification
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}, 200

Use 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.

Retry Policy

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.

AttemptDelay after previous attemptCumulative wait
1stImmediate0 s
2nd30 seconds30 s
3rd5 minutes5 min 30 s
4th (final)30 minutes35 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.

Testing Webhooks

Option 1 — webhook.site (zero setup)

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.

Register webhook.site URL
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"
  }'

Option 2 — ngrok (tunnel to localhost)

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.

bash
# 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"
  }'

Option 3 — Manual event dispatch

Send a test event to any registered endpoint without waiting for a real email to arrive.

Trigger a test event
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" }'

Webhook API Endpoints

GET/v1/webhooksList all registered webhook endpoints
POST/v1/webhooksRegister a new webhook endpoint
GET/v1/webhooks/:idRetrieve endpoint details and delivery stats
PATCH/v1/webhooks/:idUpdate URL, events, or secret
DELETE/v1/webhooks/:idRemove a webhook endpoint
POST/v1/webhooks/:id/testSend a test event to the endpoint
GET/v1/webhooks/:id/deliveriesList delivery attempts and their status
POST/v1/webhooks/deliveries/:id/replayReplay a failed delivery

Next Steps

Need Help?

Our team is here to help. Reach out if you have any questions.

Contact Support