Webhooks

Configure webhooks to receive real-time email events

Webhooks allow you to receive real-time notifications about email events, enabling you to build reactive applications and track email engagement.

Overview

Mailtura can send webhook notifications for various email events:

  • sent: Email successfully sent
  • delivered: Email delivered to recipient’s server
  • opened: Recipient opened the email
  • clicked: Recipient clicked a link
  • bounced: Email bounced (hard or soft)
  • complained: Recipient marked as spam
  • failed: Email failed to send

Configuring Webhooks

Create a Webhook Endpoint

curl -X POST https://your-mailtura-instance.com/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Tenant-ID: acme-corp" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/mailtura",
    "events": ["delivered", "opened", "clicked", "bounced"],
    "enabled": true
  }'

Webhook Configuration

  • url: Your endpoint URL (must be HTTPS)
  • events: Array of events to subscribe to
  • enabled: Enable or disable the webhook
  • secret: Optional webhook signing secret

Webhook Payload

Event Structure

All webhook events follow this structure:

{
  "id": "evt_123456",
  "type": "email.delivered",
  "timestamp": "2024-12-30T10:00:00Z",
  "data": {
    "emailId": "msg_123456",
    "recipient": "user@example.com",
    "subject": "Welcome to Acme Corp"
  }
}

Delivered Event

{
  "id": "evt_123456",
  "type": "email.delivered",
  "timestamp": "2024-12-30T10:00:00Z",
  "data": {
    "emailId": "msg_123456",
    "recipient": "user@example.com",
    "subject": "Welcome to Acme Corp",
    "deliveredAt": "2024-12-30T10:00:05Z"
  }
}

Opened Event

{
  "id": "evt_123457",
  "type": "email.opened",
  "timestamp": "2024-12-30T10:05:00Z",
  "data": {
    "emailId": "msg_123456",
    "recipient": "user@example.com",
    "openedAt": "2024-12-30T10:05:00Z",
    "userAgent": "Mozilla/5.0...",
    "ipAddress": "192.168.1.1"
  }
}

Clicked Event

{
  "id": "evt_123458",
  "type": "email.clicked",
  "timestamp": "2024-12-30T10:10:00Z",
  "data": {
    "emailId": "msg_123456",
    "recipient": "user@example.com",
    "url": "https://example.com/landing",
    "clickedAt": "2024-12-30T10:10:00Z",
    "userAgent": "Mozilla/5.0...",
    "ipAddress": "192.168.1.1"
  }
}

Bounced Event

{
  "id": "evt_123459",
  "type": "email.bounced",
  "timestamp": "2024-12-30T10:00:10Z",
  "data": {
    "emailId": "msg_123456",
    "recipient": "user@example.com",
    "bounceType": "hard",
    "reason": "Mailbox does not exist",
    "diagnosticCode": "550 5.1.1"
  }
}

Implementing a Webhook Handler

Node.js with Express

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

app.post('/webhooks/mailtura', (req, res) => {
  const signature = req.headers['x-mailtura-signature'];

  // Verify webhook signature
  if (!verifySignature(req.body, signature)) {
    return res.status(401).send('Invalid signature');
  }

  const event = req.body;

  switch (event.type) {
    case 'email.delivered':
      console.log('Email delivered:', event.data.emailId);
      break;
    case 'email.opened':
      console.log('Email opened:', event.data.emailId);
      break;
    case 'email.clicked':
      console.log('Link clicked:', event.data.url);
      break;
    case 'email.bounced':
      console.log('Email bounced:', event.data.reason);
      break;
  }

  res.status(200).send('OK');
});

function verifySignature(payload, signature) {
  const secret = process.env.MAILTURA_WEBHOOK_SECRET;
  const hash = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  return hash === signature;
}

app.listen(3000);

Python with Flask

from flask import Flask, request, jsonify
import hmac
import hashlib
import json

app = Flask(__name__)

@app.route('/webhooks/mailtura', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Mailtura-Signature')

    # Verify webhook signature
    if not verify_signature(request.data, signature):
        return jsonify({'error': 'Invalid signature'}), 401

    event = request.json

    if event['type'] == 'email.delivered':
        print(f"Email delivered: {event['data']['emailId']}")
    elif event['type'] == 'email.opened':
        print(f"Email opened: {event['data']['emailId']}")
    elif event['type'] == 'email.clicked':
        print(f"Link clicked: {event['data']['url']}")
    elif event['type'] == 'email.bounced':
        print(f"Email bounced: {event['data']['reason']}")

    return jsonify({'status': 'success'}), 200

def verify_signature(payload, signature):
    secret = os.environ['MAILTURA_WEBHOOK_SECRET']
    hash_object = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    )
    return hash_object.hexdigest() == signature

if __name__ == '__main__':
    app.run(port=3000)

Security

Verify Signatures

Always verify webhook signatures to ensure authenticity:

  1. Retrieve the signature from the X-Mailtura-Signature header
  2. Compute HMAC SHA256 of the raw request body using your secret
  3. Compare the computed hash with the received signature

Use HTTPS

Webhook URLs must use HTTPS to ensure data security.

Validate Timestamps

Check the timestamp field to reject old webhook events:

const eventAge = Date.now() - new Date(event.timestamp).getTime();
const maxAge = 5 * 60 * 1000; // 5 minutes

if (eventAge > maxAge) {
  return res.status(400).send('Event too old');
}

Managing Webhooks

List Webhooks

curl https://your-mailtura-instance.com/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Tenant-ID: acme-corp"

Update Webhook

curl -X PATCH https://your-mailtura-instance.com/api/v1/webhooks/wh_123456 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Tenant-ID: acme-corp" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["delivered", "bounced"]
  }'

Delete Webhook

curl -X DELETE https://your-mailtura-instance.com/api/v1/webhooks/wh_123456 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Tenant-ID: acme-corp"

Testing Webhooks

Send Test Event

curl -X POST https://your-mailtura-instance.com/api/v1/webhooks/wh_123456/test \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Tenant-ID: acme-corp" \
  -H "Content-Type: application/json" \
  -d '{
    "eventType": "email.delivered"
  }'

Use Webhook Testing Tools

Best Practices

  1. Handle retries: Implement idempotency to handle duplicate events
  2. Respond quickly: Return 200 status within 5 seconds
  3. Process asynchronously: Queue webhook processing for heavy operations
  4. Monitor failures: Track webhook delivery failures
  5. Log events: Keep records of all webhook events for debugging

Next Steps