How-to guide

How to Verify a Webhook Signature

A webhook signature is an HMAC of the request body computed with a secret only you and the sender know. To verify one, compute the HMAC over the exact raw body using the algorithm the provider specifies, then compare it with the value in the header using a timing-safe comparison. Sign the raw bytes — re-serialising the JSON first is what breaks most implementations.

HMAC Generator

Sign a message with a secret key using HMAC.

Open the tool

A webhook arrives as an ordinary HTTP request, so anyone who learns the URL can send one. The signature is what proves the sender holds the shared secret. Without verification, a webhook endpoint is an unauthenticated write API pointed at your database.

Almost every failed verification comes down to signing something slightly different from what the sender signed. The algorithm is rarely the problem; the exact bytes usually are.

Step by step

  1. Capture the raw body, before any parsing

    This is the step that catches people. Most frameworks parse JSON into an object before your handler runs, and re-serialising it changes key order, whitespace and number formatting. The signature covers the original bytes, so you must capture them before parsing.

  2. Build the signed string exactly as documented

    Some providers sign the body alone. Others sign a timestamp joined to the body with a separator, or prefix a version marker. Read the documentation rather than assuming, because a missing prefix produces a valid HMAC of the wrong thing.

  3. Compute the HMAC with the right algorithm and encoding

    HMAC-SHA256 is the common choice. Note whether the provider sends hex or Base64 — comparing a hex digest against a Base64 one always fails, and it is an easy mistake because both look like noise.

  4. Compare in constant time

    Use a timing-safe comparison such as hash_equals or crypto.timingSafeEqual, never ==. An ordinary comparison returns as soon as it finds a differing byte, and that timing difference can leak the expected signature one byte at a time.

  5. Reject stale timestamps

    A valid signature stays valid forever, so an intercepted request can be replayed. If the provider includes a timestamp in the signed string, reject anything older than a few minutes.

  6. Debug a mismatch by reproducing it

    Take the exact raw body and your secret, compute the HMAC by hand, and compare it with the header. If they differ, the signed string is wrong — check for a re-serialised body, a missing timestamp prefix, or a trailing newline.

Example

A common signed-string format: version, timestamp and raw body joined by full stops.

Signed string

v1.1754521200.{"event":"order.paid","id":42}

Secret:    whsec_abc123
Algorithm: HMAC-SHA256, hex

Header sent

X-Signature: v1,t=1754521200,
  s=4f8a2c...  (64 hex characters)

Verify by recomputing s over the
same string and comparing safely.

Frequently asked questions

Why does my signature never match?
Nine times out of ten the body was parsed and re-serialised before signing. JSON.stringify of a parsed object is not byte-identical to what arrived — key order, spacing and number formatting can all differ. Capture the raw body before any middleware touches it.
Why use HMAC rather than hashing the secret and body together?
Because SHA-256(secret + body) is vulnerable to a length-extension attack: an attacker who has the digest and the body length can append data and compute a valid digest without knowing the secret. HMAC uses a nested construction that prevents it.
Does the comparison really need to be timing-safe?
Yes. A normal comparison stops at the first differing byte, so the time it takes reveals how many leading bytes were correct. That is enough to recover a signature byte by byte over many attempts. Use hash_equals, crypto.timingSafeEqual or your language's equivalent.
What stops someone replaying a captured webhook?
Nothing, unless you check freshness. The signature stays valid indefinitely. If the provider signs a timestamp, reject requests older than a few minutes; otherwise record delivery IDs and ignore repeats.
Should I verify before or after parsing the body?
Before doing anything else with it. Verification is what tells you the payload is trustworthy, so parsing, logging or acting on an unverified body means processing data from an unauthenticated source.

Tools used in this guide

All tools →
All guides →

Last reviewed .