Webhooks

Get notified when email events happen.

Event Types

EventDescription
email.sentEmail was sent to the mail server
email.deliveredEmail was delivered to recipient's inbox
email.bouncedEmail bounced (hard or soft)
email.complainedRecipient marked email as spam
email.openedEmail was opened by recipient
email.clickedLink 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: 1701234567

Body

{
  "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 id for idempotency
  • Verify signatures: Always validate the HMAC signature
  • Use HTTPS: Webhook URLs must use HTTPS