verify webhooks
Testing tip: create a throwaway endpoint at freewebhook.app and point a Mailway webhook at it. You'll see every delivery — headers, signature, and payload — arrive in real time, which makes wiring up your verification code far easier than guessing at logs.
Every delivery is signed with the Standard Webhooks scheme — the same one Svix popularized — so existing verification libraries work unchanged. Your endpoint receives three headers:
webhook-id: <event id> webhook-timestamp: <unix seconds> webhook-signature: v1,<base64 hmac-sha256>
verifying with a library (recommended)
import { Webhook } from "svix"; // or standardwebhooks
const wh = new Webhook(process.env.MAILWAY_WEBHOOK_SECRET); // whsec_...
const payload = wh.verify(rawBody, {
"webhook-id": req.headers["webhook-id"],
"webhook-timestamp": req.headers["webhook-timestamp"],
"webhook-signature": req.headers["webhook-signature"],
}); // throws if invalidverifying by hand
const crypto = require("crypto");
const secret = Buffer.from(process.env.MAILWAY_WEBHOOK_SECRET.slice(6), "base64");
const signed = `${id}.${timestamp}.${rawBody}`;
const expected = "v1," + crypto.createHmac("sha256", secret).update(signed).digest("base64");
// timing-safe compare against webhook-signaturepayload shape
{
"type": "email.delivered",
"created_at": "2026-08-05T12:00:00Z",
"data": { "email_id": "…", ... }
}retries & the circuit breaker
Failed deliveries retry on a backoff ladder (5s → 5m → 30m → 2h → 5h → 10h). After 10 consecutive failures the endpoint is auto-disabled; redelivering any attempt from the dashboard re-enables it. Respond with a 2xx within 10 seconds — do heavy work async.