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:
- Retrieve the signature from the
X-Mailtura-Signatureheader - Compute HMAC SHA256 of the raw request body using your secret
- 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
- Webhook.site - Inspect webhook payloads
- ngrok - Expose local server for testing
- RequestBin - Collect webhook requests
Best Practices
- Handle retries: Implement idempotency to handle duplicate events
- Respond quickly: Return 200 status within 5 seconds
- Process asynchronously: Queue webhook processing for heavy operations
- Monitor failures: Track webhook delivery failures
- Log events: Keep records of all webhook events for debugging