Webhooks
Real-time event notifications for agent mailboxes with HMAC verification.
Overview
Webhooks deliver real-time HTTP POST notifications to your endpoint when events occur in an agent mailbox — new messages, bounces, delivery confirmations, and more.
Create a Webhook
const webhook = await tx.agentEmail.webhooks.create({
mailbox: mailbox.id,
url: 'https://yourapp.com/webhooks/agent-email',
events: ['message.received', 'message.bounced', 'message.delivered'],
secret: 'whsec_your_signing_secret', // Optional — auto-generated if omitted
});
console.log(webhook.id); // whk_abc123
console.log(webhook.secret); // whsec_...Event Types
| Event | Description |
|---|---|
message.received | New inbound email received in the mailbox |
message.delivered | Outbound email successfully delivered |
message.bounced | Outbound email bounced (hard or soft) |
message.deferred | Outbound email delivery deferred (will retry) |
message.complained | Recipient marked email as spam |
message.opened | Recipient opened the email (if tracking enabled) |
message.clicked | Recipient clicked a link (if tracking enabled) |
Webhook Payload
All events follow a consistent structure:
{
"id": "evt_abc123",
"type": "message.received",
"createdAt": "2025-01-10T14:30:00Z",
"data": {
"messageId": "msg_xyz789",
"mailboxId": "mbx_abc123",
"from": "user@example.com",
"to": ["agent@agentmail.usetransactional.com"],
"subject": "Re: Research Request",
"textPreview": "Thanks for the update...",
"threadId": "thd_def456",
"receivedAt": "2025-01-10T14:30:00Z"
}
}For message.bounced events, additional fields are included:
{
"type": "message.bounced",
"data": {
"messageId": "msg_xyz789",
"bounceType": "hard",
"bounceCode": "550",
"bounceMessage": "Mailbox not found",
"recipient": "invalid@example.com"
}
}HMAC Verification
Every webhook request includes an X-Transactional-Signature header containing an HMAC-SHA256 signature. Always verify this signature to ensure the request is authentic.
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string,
): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}
// In your webhook handler
app.post('/webhooks/agent-email', (req, res) => {
const signature = req.headers['x-transactional-signature'] as string;
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET,
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = req.body;
// ...
res.status(200).send('OK');
});Retry Policy
Failed webhook deliveries (non-2xx response or timeout) are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 12 hours |
After 5 failed attempts, the event is dropped and the webhook is marked as failing. You can view failed deliveries in the dashboard.
Manage Webhooks
// List webhooks for a mailbox
const webhooks = await tx.agentEmail.webhooks.list({
mailbox: mailbox.id,
});
// Update a webhook
await tx.agentEmail.webhooks.update(webhookId, {
events: ['message.received'],
url: 'https://yourapp.com/webhooks/v2/agent-email',
});
// Delete a webhook
await tx.agentEmail.webhooks.delete(webhookId);
// Test a webhook (sends a test event)
await tx.agentEmail.webhooks.test(webhookId);Next Steps
- Receiving Email — Inbound processing details
- Custom Domains — BYOD configuration
- API Reference — Webhook endpoints