- webhooks
- API security
- developer guide
- event-driven systems
Webhook Signature Verification: Protect Events from Spoofing and Replay
, by System Admin

A webhook receiver should not trust a request just because it reached the right URL. A sender’s signature lets your service check whether the request body matches a message signed with a secret shared by that sender. For example, Stripe signs webhook events and documents verification with its endpoint secret and the Stripe-Signature header.
Verify the exact bytes the sender signed
Signature verification depends on the original request body. Parsing JSON and then serialising it again can change whitespace, key order or encoding, which changes the bytes and invalidates the signature. Read and retain the raw body for verification, and use the provider’s maintained SDK when available rather than writing a custom verifier casually.
Stripe’s guide describes passing the raw request body, signature header and endpoint secret to its library. It also warns that middleware which parses or modifies the body before verification can cause verification failures: Stripe signature verification guidance.
Check the signature before acting
Reject requests with a missing or invalid signature before performing any consequential operation. Keep endpoint secrets out of source control and logs, and use the secret associated with the correct endpoint and environment. If verification fails, return an appropriate failure response and record enough safe diagnostic context to investigate without logging credentials or sensitive payload data.
If you implement verification yourself, follow the provider’s exact signing specification: algorithm, signed message format, header parsing and comparison rules all matter. Use a constant-time comparison for secret-derived signatures. A handmade approximation is not interchangeable with the provider’s documented scheme.
Use timestamps to limit replay
A captured, valid signed request could otherwise be sent again. Where the provider includes a signed timestamp, validate that it falls within the provider’s allowed tolerance. Stripe explains that its timestamp is included in the signed payload and its libraries use a default tolerance of five minutes; accurate server time is therefore important. Follow the sending provider’s rules rather than assuming every webhook uses the same header or tolerance.
Timestamp checks limit how long a captured request remains acceptable, but they do not by themselves guarantee that your business logic runs only once. Retries are normal in webhook delivery, so receivers also need duplicate-event handling.
Make event handling idempotent
Persist a stable provider event identifier and enforce uniqueness before applying a non-repeatable action. If the same event arrives again, acknowledge it without granting access, charging, provisioning or sending the same notification twice. For work that takes longer than a quick request, verify the signature, store the event safely, enqueue processing and return a successful response only after you have durably accepted it. Design the worker to tolerate retries too.
Stripe notes that it creates a new signature and timestamp for each retry attempt. That delivery detail does not remove the need to deduplicate the underlying event.
Test the unhappy paths
- Valid signature with the expected raw body.
- Modified body, missing header, wrong secret and malformed signature.
- Timestamp outside the allowed tolerance, if the provider signs timestamps.
- The same event delivered more than once, including concurrent attempts.
- Temporary database or worker failure, followed by a retry.
Use test events and the provider’s tools or SDK fixtures where available. Avoid putting real signing secrets or unredacted customer payloads in test logs.
Production checklist
- Verify the provider’s signature against the unmodified request body.
- Use the correct endpoint secret and keep it private.
- Enforce signed-timestamp tolerance when the provider supports it.
- Deduplicate events using a durable, stable event identifier.
- Separate fast acceptance from slower processing with a durable queue when needed.
- Test retries, duplicates and failure recovery before relying on the integration.
Signature verification answers “Was this request signed as expected?” Idempotency answers “Have I already handled this event?” Reliable webhook consumers need both.