Learn how to receive and process webhook events from Transactional. Track email opens, clicks, bounces, and more in real-time.
Intermediate
20 minutes
webhooks
events
tracking
real-time
Prerequisites
Node.js 18+ installed
Transactional account with API key
Public HTTPS endpoint for webhook delivery
CODE
Full Example
import { Transactional, WebhookEvent } from '@transactional/sdk';
import express from 'express';
const app = express();
const client = new Transactional({
apiKey: process.env.TRANSACTIONAL_API_KEY!,
});
const WEBHOOK_SECRET = process.env.TRANSACTIONAL_WEBHOOK_SECRET!;
app.post('/webhooks/transactional', express.raw({ type: 'application/json' }), async (req, res) => {
const signature = req.headers['x-transactional-signature'] as string;
const timestamp = req.headers['x-transactional-timestamp'] as string;
const isValid = client.webhooks.verify({
payload: req.body,
signature,
timestamp,
secret: WEBHOOK_SECRET,
});
if (!isValid) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body.toString()) as WebhookEvent;
try {
await handleWebhookEvent(event);
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook handling error:', error);
res.status(500).json({ error: 'Processing failed' });
}
});
async function handleWebhookEvent(event: WebhookEvent) {
console.log(`Received event: ${event.type}`);
switch (event.type) {
case 'email.delivered':
await handleDelivered(event.data);
break;
case 'email.opened':
await handleOpened(event.data);
break;
case 'email.clicked':
await handleClicked(event.data);
break;
case 'email.bounced':
await handleBounced(event.data);
break;
case 'email.complained':
await handleComplained(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
}
async function handleDelivered(data: WebhookEvent['data']) {
console.log(`Email ${data.emailId} delivered to ${data.recipient}`);
}
async function handleOpened(data: WebhookEvent['data']) {
console.log(`Email ${data.emailId} opened by ${data.recipient}`);
}
async function handleClicked(data: WebhookEvent['data']) {
console.log(`Link clicked in ${data.emailId}: ${data.url}`);
}
async function handleBounced(data: WebhookEvent['data']) {
console.log(`Email ${data.emailId} bounced: ${data.bounceType}`);
if (data.bounceType === 'hard') {
await removeFromMailingList(data.recipient);
}
}
async function handleComplained(data: WebhookEvent['data']) {
console.log(`Spam complaint from ${data.recipient}`);
await unsubscribeUser(data.recipient);
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
Understanding Webhooks
Webhooks are HTTP callbacks that notify your application when events occur. Instead of polling our API for updates, we push events to your server in real-time.
Available Events
Event Type
Description
When It Fires
email.sent
Email accepted for delivery
Immediately after API call
email.delivered
Email delivered to recipient server
Seconds to minutes after send
email.opened
Recipient opened the email
When tracking pixel loads
email.clicked
Recipient clicked a link
When tracked link is clicked
email.bounced
Email could not be delivered
Usually within minutes
email.complained
Recipient marked as spam
When ISP reports complaint
email.unsubscribed
Recipient clicked unsubscribe
When unsubscribe link clicked
Webhook Security
Signature Verification
Always verify webhook signatures to ensure requests come from Transactional:
const isValid = client.webhooks.verify({ payload: rawBody, // Raw request body as string signature: signatureHeader, // X-Transactional-Signature header timestamp: timestampHeader, // X-Transactional-Timestamp header secret: webhookSecret, // Your webhook secret});
Replay Attack Prevention
The timestamp header prevents replay attacks. Reject events older than 5 minutes: