Skip to content

Webhook Payload Builder

Build a webhook payload, sign it with a real HMAC-SHA256 in four common schemes, and verify a signature your endpoint already rejected.

Payload signed
494 bytes

HMAC-SHA256 computed in your browser over the exact bytes shown below.

Header
Stripe-Signature
Body bytes
494
Scheme
stripe
Signed timestamp
1785312000
That timestamp reads as
2026-07-29T08:00:00.000Z
Worth knowing about this payload:
  • The signature covers these exact bytes, indentation included. Real senders transmit compact JSON — if your receiver re-serialises the body before verifying, it will compute a different signature and this is where that bug lives.
What this assumes: the HMAC is real: SHA-256 and HMAC are implemented on this page and run over the exact bytes of the body shown above, so the value is the one your receiver will compute if it reads the same bytes with the same secret. What is NOT real is the delivery — nothing is sent from here, and the curl command is for you to run. The three named schemes follow the published conventions of well-known senders; they are conventions rather than a standard, so check your own receiver's documentation before assuming the header name. Everything on this page runs in your browser. Nothing you paste is uploaded, logged or sent anywhere.

Webhook signature verification fails in the same three ways for everyone, and none of them are cryptography. This page exists to tell those three apart, which is why it verifies as well as signs: paste the header your endpoint rejected, and if it matches what this page computes, the HMAC is fine and the bug is elsewhere.

The bug that is always the bug

A signature covers bytes, and a JSON body parsed and re-serialised is a different sequence of bytes with the same meaning — keys reordered, whitespace gone, a float rendered differently. Every framework that hands you a parsed body has already destroyed the thing you need to verify. The fix is to read the raw body before any JSON middleware touches it, and the toggle above exists so you can watch the signature change when the formatting does.

Why the timestamp is signed too

A signature over the body alone is replayable forever: anyone who captures one valid delivery can send it again next year and it will verify. Signing the timestamp alongside the body, and rejecting anything outside a few minutes, is what closes that — which is why the Stripe and Standard Webhooks schemes concatenate the timestamp into the signed string rather than merely sending it. If you are designing a sender, do the same; if you are receiving, check the window as well as the digest.

Use a test secret

Everything here runs in your browser and nothing is transmitted, which is true and is not a reason to paste a live signing secret into a web page. Use the one from your test environment. If you are debugging a production failure, rotate afterwards — the cost is a few minutes and the alternative is a secret whose history now includes a text field.

The reasoning behind this

Webhook Payload Builder · Multigrid