All field notes
Webhook guide 3 min read

Webhook Signature Verification: HMAC Testing Guide

Understand how webhook signatures work, why verification fails, and how to test valid, invalid, stale, and replayed deliveries without exposing a signing secret.

Published August 2, 2026By Webhook Tester
webhook signature verificationwebhook signature validationwebhook signature verification failedwebhook signature hmacwebhook signature headerverify webhook signaturewebhook security
01

What a Webhook Signature Proves

A webhook signature lets a receiver test whether a request was created by someone who knows the shared signing secret and whether the signed bytes changed in transit. It does not encrypt the payload or make sensitive data safe to log.

Providers commonly compute an HMAC over the raw request body and place the digest in a signature header. Some schemes also sign a timestamp, version marker, or delivery identifier to make replay checks possible.

Treat the provider documentation as the exact contract. Header names, digest encoding, signed content, timestamp format, and secret scope differ, so a verifier copied from another provider can appear correct while rejecting every delivery.

02

Verify the Exact Raw Request Body

Signature verification must run before a JSON parser, body normalizer, or character conversion changes the request. Even harmless formatting differences can produce a completely different HMAC.

Read the raw bytes once, enforce a payload-size limit, compute the expected digest with the endpoint secret, and compare the decoded values with a timing-safe function. Parse JSON only after the signature is valid.

Use an official provider SDK or verified middleware when it handles the correct raw-body path for your framework. Still test the integration, because middleware order and proxy behavior can change what reaches the verifier.

Provider-neutral verification sequence
raw_body = read_request_bytes_with_limit()
signature = read_provider_signature_header()
expected = HMAC_SHA256(endpoint_secret, raw_body)

if !constant_time_equal(decode(signature), expected):
    return 401

event = parse_and_validate_json(raw_body)
03

Validate Timestamps and Limit Replays

When a provider includes a signed timestamp, reject deliveries outside a documented tolerance after accounting for normal clock skew. The timestamp must be part of the signed message; an unsigned timestamp can be replaced by an attacker.

Timestamp checks reduce replay exposure but do not replace idempotency. A valid request can be delivered twice inside the accepted time window, and providers intentionally retry events when acknowledgements fail.

Store a stable delivery or event ID and enforce uniqueness before business side effects. A repeated valid event should return a successful no-op after confirming that the original processing result is durable.

  • Synchronize server clocks
  • Use the provider-defined signed timestamp format
  • Choose the documented tolerance instead of inventing one
  • Deduplicate valid events independently of timestamp checks
04

Diagnose Verification Failures Safely

First confirm that the endpoint secret belongs to the exact webhook destination and environment. Test and live modes, rotated secrets, and multiple destinations often use different values.

Next compare byte counts and content type before parsing. Log a request correlation ID, algorithm, header presence, body length, and verification outcome, but never log the secret or a full expected digest.

Use a synthetic request captured from the provider and replay it only in an isolated test environment. If you edit its JSON, generate a new signature with a test-only secret rather than expecting the original signature to remain valid.

  • Wrong or stale endpoint secret
  • Parsed body used instead of raw bytes
  • Hex, Base64, or prefix decoded incorrectly
  • Proxy or middleware changed the body
  • Timestamp tolerance or server clock is wrong
05

Build a Complete Signature Test Matrix

A useful test suite includes one valid signature and deliberate failures for a changed body, changed header, wrong secret, truncated digest, unsupported algorithm, stale timestamp, and duplicate delivery ID.

Assert that invalid requests stop before JSON-driven routing or side effects. Return an intentional client error without revealing which secret-derived value differed, and keep the response body generic.

Rotate a test secret and prove that the new value succeeds while the old value fails. Document the rotation and rollback process before production so credential changes do not create an avoidable delivery outage.

Common questions

Frequently asked questions

How does webhook signature verification work?
The provider and receiver share a secret. The provider signs the exact request body, often with HMAC-SHA256, and sends the result in a header. The receiver computes its own digest and compares the two values in constant time before trusting the event.
Why does webhook signature verification fail?
Common causes are using the wrong endpoint secret, verifying parsed or reformatted JSON instead of raw bytes, reading the wrong signature header, decoding the digest incorrectly, or rejecting a valid timestamp because server clocks differ.
Should I store webhook secrets in a tester?
No. Keep signing secrets in a secret manager or protected environment configuration. A public request inspector can display a sample header and body, but it should never receive the secret used to calculate or verify the signature.
What is constant-time signature comparison?
It is a comparison routine whose timing does not reveal where two secret-derived values differ. Use the timing-safe comparison function supplied by your language or provider SDK instead of comparing digest strings with ordinary equality.

Primary sources

Official references

Try it with a real request

Turn the guide into a test.

Open a temporary endpoint, send your payload, and inspect exactly what arrived.

Open the tester