Webhooks
Webhooks send real-time HTTP POST notifications to your server when email events occur. All payloads are signed with HMAC-SHA256 for verification.
Endpoints
GET/api/webhooks
POST/api/webhooks
DELETE/api/webhooks/:id
Create a Webhook
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | Required | HTTPS endpoint URL to receive events |
| events | string[] | Required | Array of event types to subscribe to |
Create webhook
curl -X POST https://fwd.sarthak.online/api/webhooks \
-H "Cookie: your-session-cookie" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/fwd",
"events": ["email.delivered", "email.opened", "email.bounced"]
}'The response includes a secret (prefixed with whsec_) used to verify webhook signatures.
Event Types
| Event | Description |
|---|---|
| email.sent | Email accepted and queued for delivery |
| email.delivered | Email successfully delivered to the recipient's mail server |
| email.opened | Recipient opened the email (via tracking pixel) |
| email.clicked | Recipient clicked a tracked link in the email |
| email.bounced | Email bounced — address added to suppression list |
| email.complained | Recipient marked the email as spam |
| email.unsubscribed | Recipient clicked the unsubscribe link |
Webhook Payload
Each webhook delivery includes these headers and a JSON body:
| Header | Description |
|---|---|
| X-Fwd-Event | The event type (e.g., email.delivered) |
| X-Fwd-Signature | HMAC-SHA256 signature for verification |
| Content-Type | application/json |
Example payload
{
"emailId": "email_abc123",
"to": "user@example.com",
"timestamp": "2025-01-15T12:00:00.000Z"
}Signature Verification
The X-Fwd-Signature header contains a timestamp and HMAC-SHA256 hash in the format: t={timestamp},v1={hash}. Verify it to ensure the webhook is authentic.
Node.js verification example
const crypto = require('crypto');
function verifyWebhookSignature(req, secret) {
const signature = req.headers['x-fwd-signature'];
const [tPart, vPart] = signature.split(',');
const timestamp = tPart.replace('t=', '');
const receivedHash = vPart.replace('v1=', '');
const payload = `${timestamp}.${JSON.stringify(req.body)}`;
const expectedHash = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (expectedHash !== receivedHash) {
throw new Error('Invalid webhook signature');
}
// Optional: reject old webhooks (>5 min)
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
if (age > 300) {
throw new Error('Webhook timestamp too old');
}
return true;
}Limits
Maximum 5 webhooks per account on the Free plan, 10 on Pro. Localhost URLs are blocked in production for security.