docs
v1 Pricing Dashboard
Concepts / webhooks

Webhooks

HTTPS push for the moments something arrives: signed deliveries with honest retries, and a hard rule that message bodies never ride in them.

When to use them (and when not to)

Tests should long-poll — a wait= request is simpler, faster, and needs no public endpoint. Webhooks are for the glue around tests: notifying a Slack channel, kicking a downstream job when a domain verifies, feeding messages into a pipeline. Webhooks ship on paid plans — 3 endpoints on solo, 10 on team, 25 on scale.

Events

Three event types today: message.received (an email landed — summary fields plus extraction results), sms.received (same, for inbound SMS), and domain.verified (a custom domain finished DNS verification).

One rule with no exceptions: bodies never ride in webhooks. Payloads carry subjects, senders, and extracted OTPs and links — not text_body, not html_body, not an SMS text. Your email is customer data and your webhook receiver's logs shouldn't become a copy of it; when a consumer needs content, it fetches the message by id over the authenticated API.

The envelope

POST to your endpoint
{
  "id": "6b9f42e0-…",      // delivery id — your idempotency key
  "type": "message.received",
  "created_at": "2026-07-07T14:18:02Z",
  "data": { …summary + extracted… }
}

id is the delivery's UUID and stays the same across retries of that delivery — deduplicate on it and retries become harmless. It also arrives as the X-MailFixture-Delivery header.

Signatures

Each endpoint gets a whsec_… secret, shown once at creation. Every delivery is signed:

header
X-MailFixture-Signature: t=1751897882,v1=5257a869e7…

v1 is HMAC-SHA256(secret, "<t>.<body>") in hex — deliberately the same scheme Stripe uses, so verification code you already run for Stripe ports by changing the header name. Both SDKs ship a verify_webhook_signature helper. Verify on every request, and reject stale timestamps (a few minutes is plenty) to shut out replays.

Delivery and retries

Endpoints must be HTTPS on a publicly routable address; deliveries follow no redirects and time out after 5 seconds. Answer any 2xx to acknowledge — do the real work async if it's slow.

Anything else is retried with backoff: up to 8 attempts at roughly 30s, 2m, 10m, 30m, 2h, 6h, and 12h after the first — about 21 hours of trying. If 5 deliveries in a row exhaust all attempts, the endpoint is auto-disabled so a dead receiver doesn't burn retries forever; fix it, then re-enable from the dashboard or POST /v1/webhooks/{id}/enable.

Each endpoint keeps 7 days of delivery history — every attempt with its response code — in the dashboard and at GET /v1/webhooks/{id}/deliveries. POST /v1/webhooks/{id}/test fires a test event to check your wiring before anything real depends on it.

tip Building the receiver? Start with the test event and the delivery history side by side: send, look at the recorded response code, fix, repeat. It's the fastest debugging loop we know for webhook plumbing.

Next

Messages
Long-polling — the test-side primitive.
Custom domains
The domain.verified event's subject.
Pricing
Endpoint counts by plan.
Was this page useful? yes no Quickstart: Playwright →
ON THIS PAGE
edit this page ↗
report an issue ↗