Webhooks
Get notified when email events happen.
Event Types
| Event | Description |
|---|---|
| email.sent | Email was sent to the mail server |
| email.delivered | Email was delivered to recipient's inbox |
| email.bounced | Email bounced (hard or soft) |
| email.complained | Recipient marked email as spam |
| email.opened | Email was opened by recipient |
| email.clicked | Link in email was clicked |
Creating a Webhook
curl -X POST https://tigermail.io/api/webhooks \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/tigermail",
"events": ["email.delivered", "email.bounced"]
}'Payload Format
Each webhook delivery includes these headers and body:
Headers
Content-Type: application/json
X-TigerMail-Signature: ts=1701234567;h1=abc123...
X-TigerMail-Timestamp: 1701234567Body
{
"id": "evt_abc123def456",
"timestamp": "2024-12-02T10:30:00.000Z",
"type": "email.delivered",
"data": {
"emailId": "clx1234567890",
"messageId": "0100018f-1234-5678-9abc-def012345678",
"to": ["user@example.com"],
"subject": "Your Invoice",
"status": "DELIVERED",
"timestamp": "2024-12-02T10:30:00.000Z"
}
}Bounce Event Data
Bounce events include additional fields:
{
"type": "email.bounced",
"data": {
"emailId": "clx1234567890",
"to": ["invalid@example.com"],
"subject": "Your Invoice",
"status": "BOUNCED",
"bounceType": "Permanent",
"bounceSubType": "General",
"timestamp": "2024-12-02T10:30:00.000Z"
}
}bounceType: "Permanent" (hard bounce), "Transient" (soft bounce), or "Undetermined"
Verifying Signatures
Always verify webhook signatures to ensure requests come from TigerMail. The signature is in the X-TigerMail-Signature header.
Verify Webhook Signature
Add signature verification to your webhook endpoint
Ready-to-use instructions for AI coding assistants
Click to view full instructions
import crypto from "crypto";
function verifySignature(payload, signature, secret) {
// Parse the signature header
const parts = Object.fromEntries(
signature.split(";").map((p) => p.split("="))
);
const timestamp = parts.ts;
const hash = parts.h1;
// Compute expected signature
const signaturePayload = `${timestamp}:${payload}`;
const expected = crypto
.createHmac("sha256", secret)
.update(signaturePayload)
.digest("hex");
// Verify (use timing-safe comparison)
return crypto.timingSafeEqual(
Buffer.from(hash),
Buffer.from(expected)
);
}
// In your webhook handler:
app.post("/webhooks/tigermail", (req, res) => {
const signature = req.headers["x-tigermail-signature"];
const payload = JSON.stringify(req.body);
if (!verifySignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// Process the webhook...
res.status(200).send("OK");
});Retry Policy
Failed webhook deliveries are retried up to 3 times with exponential backoff:
- Attempt 1: Immediate
- Attempt 2: After ~2 seconds
- Attempt 3: After ~4 seconds
Your endpoint should respond with a 2xx status code within 10 seconds.
Best Practices
- Respond quickly: Return 200 immediately, process async
- Handle duplicates: Use the event
idfor idempotency - Verify signatures: Always validate the HMAC signature
- Use HTTPS: Webhook URLs must use HTTPS