Webhooks

Mailbeam can push events to your server via HTTP webhooks. Use webhooks to receive real-time notifications when:

  • A batch verification job completes
  • A previously valid email starts hard-bouncing
  • An MX record becomes unreachable

Setting up a webhook

  1. Go to Settings → Webhooks in your dashboard
  2. Click Add endpoint
  3. Enter your HTTPS URL and select the events to receive
  4. Save — Mailbeam will send a test event immediately

Webhook endpoints must be publicly reachable HTTPS URLs. Local development? Use ngrok or Cloudflare Tunnel.

Supported events

EventWhen it fires
batch.completedA batch job finishes processing
email.bouncedA previously valid email starts hard-bouncing
email.mx_degradedMX records for a domain become unreachable
quota.thresholdYou reach 80% or 100% of your monthly quota

Payload structure

All webhook payloads follow the same envelope:

{
  "id": "evt_01hx9abc123",
  "type": "batch.completed",
  "created_at": "2025-01-15T14:32:00Z",
  "data": {
    "job_id": "job_01hx9xyz456",
    "total": 5000,
    "valid": 4213,
    "invalid": 787,
    "download_url": "https://api.mailbeam.dev/v1/jobs/job_01hx9xyz456/results"
  }
}

HMAC signature verification

Every webhook request includes an X-Mailbeam-Signature header. Always verify this signature before processing the payload.

The signature is an HMAC-SHA256 digest of the raw request body, keyed with your webhook secret.

import crypto from "crypto";

export function verifyWebhookSignature(
  body: string | Buffer,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`, "utf8"),
    Buffer.from(signature, "utf8")
  );
}

// In your Express handler:
app.post("/webhooks/mailbeam", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["x-mailbeam-signature"] as string;

  if (!verifyWebhookSignature(req.body, sig, process.env.MAILBEAM_WEBHOOK_SECRET!)) {
    return res.status(400).send("Invalid signature");
  }

  const event = JSON.parse(req.body.toString());
  // Handle event...
  res.json({ received: true });
});

Use express.raw() — not express.json(). You need the raw body bytes for signature verification. Parsing JSON first will cause signature mismatches.

Retry policy

If your endpoint returns a non-2xx status or times out (30 second timeout), Mailbeam retries the webhook:

AttemptDelay
1st retry5 minutes
2nd retry30 minutes
3rd retry2 hours
4th retry8 hours
5th retry24 hours

After 5 failed attempts, the webhook is disabled and you receive an email notification.

Responding to webhooks

Return a 2xx status as quickly as possible. Offload heavy processing to a queue:

app.post("/webhooks/mailbeam", async (req, res) => {
  // Verify signature first
  if (!verifyWebhookSignature(req.body, req.headers["x-mailbeam-signature"], secret)) {
    return res.status(400).send("Invalid signature");
  }

  // Acknowledge immediately
  res.json({ received: true });

  // Process asynchronously
  await queue.add("process-mailbeam-event", JSON.parse(req.body.toString()));
});