Webhooks
Receive real-time notifications about email events via webhooks.
Overview
Webhooks send HTTP POST requests to your server when email events occur. Use them to:
- Update your database when emails are delivered
- Trigger workflows when links are clicked
- Handle bounces and complaints automatically
- Sync email status with your CRM
Supported Events
| Event | Description |
|---|---|
sent | Email accepted for delivery |
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 |
unsubscribed | Recipient unsubscribed |
Setting Up Webhooks
Via Dashboard
- Go to Email > Servers > [Server] > Webhooks
- Click Add Webhook
- Configure the webhook:
- URL - Your endpoint URL
- Events - Which events to receive
- Secret - For signature verification
- Click Create
Via API
const webhook = await client.webhooks.create({
serverId: 'srv_123',
url: 'https://yoursite.com/webhooks/email',
events: ['delivered', 'bounced', 'complained'],
secret: 'your-webhook-secret',
});Webhook Payloads
Delivery Event
{
"event": "delivered",
"timestamp": "2024-01-15T10:30:00Z",
"messageId": "msg_abc123",
"serverId": "srv_123",
"streamId": "str_456",
"from": "hello@yourapp.com",
"to": "user@example.com",
"subject": "Welcome to our platform"
}Bounce Event
{
"event": "bounced",
"timestamp": "2024-01-15T10:30:00Z",
"messageId": "msg_abc123",
"serverId": "srv_123",
"streamId": "str_456",
"from": "hello@yourapp.com",
"to": "invalid@example.com",
"bounceType": "hard",
"bounceCode": "550",
"bounceDescription": "User not found"
}Click Event
{
"event": "clicked",
"timestamp": "2024-01-15T10:35:00Z",
"messageId": "msg_abc123",
"serverId": "srv_123",
"streamId": "str_456",
"recipient": "user@example.com",
"link": "https://yoursite.com/pricing",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0..."
}Verifying Webhooks
Always verify webhook signatures to ensure requests come from Transactional.
Signature Header
Each webhook includes a signature in the X-Webhook-Signature header:
X-Webhook-Signature: sha256=abc123...
Verification Code
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return `sha256=${expectedSignature}` === signature;
}
// Express.js example
app.post('/webhooks/email', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'] as string;
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
// Process event...
res.status(200).send('OK');
});Handling Webhooks
Respond Quickly
Return a 2xx response within 5 seconds. Process events asynchronously:
app.post('/webhooks/email', async (req, res) => {
// Acknowledge receipt immediately
res.status(200).send('OK');
// Process asynchronously
await processWebhookEvent(req.body);
});Idempotency
Webhooks may be delivered multiple times. Handle duplicates:
async function processWebhookEvent(event: WebhookEvent) {
// Check if already processed
const exists = await db.webhookEvents.findUnique({
where: { messageId_event: { messageId: event.messageId, event: event.event } }
});
if (exists) {
return; // Already processed
}
// Process and record
await db.webhookEvents.create({ data: event });
await handleEvent(event);
}Error Handling
Log errors but still return 200 to prevent retries:
app.post('/webhooks/email', async (req, res) => {
try {
await processWebhookEvent(req.body);
} catch (error) {
console.error('Webhook processing failed:', error);
// Still return 200 to acknowledge receipt
}
res.status(200).send('OK');
});Retry Policy
If your endpoint returns an error, we retry with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 8 hours |
| 7 | 24 hours |
After 7 failed attempts, the webhook is marked as failed.
Testing Webhooks
Using the Dashboard
- Go to your webhook settings
- Click Send Test Event
- Select an event type
- Check your endpoint received the event
Local Development
Use a tunnel service like ngrok for local testing:
ngrok http 3000
# Use the provided URL for your webhook endpointWebhook Logs
View webhook delivery logs in the dashboard:
- Request and response details
- Delivery status and timing
- Failed attempt history
Best Practices
- Verify signatures - Always validate webhook authenticity
- Respond quickly - Return 200 within 5 seconds
- Handle duplicates - Make processing idempotent
- Log everything - Keep records for debugging
- Monitor failures - Alert on repeated failures
Next Steps
- Tracking - Open and click tracking
- Suppressions - Handle bounces automatically
- API Reference - Webhook API documentation
On This Page
- Overview
- Supported Events
- Setting Up Webhooks
- Via Dashboard
- Via API
- Webhook Payloads
- Delivery Event
- Bounce Event
- Click Event
- Verifying Webhooks
- Signature Header
- Verification Code
- Handling Webhooks
- Respond Quickly
- Idempotency
- Error Handling
- Retry Policy
- Testing Webhooks
- Using the Dashboard
- Local Development
- Webhook Logs
- Best Practices
- Next Steps