Webhook Signature Verification With Replay Protection

Verify webhook signatures, enforce timestamp windows, and make event processing idempotent so captured deliveries cannot be replayed.

In this article

Webhook Signature Verification With Replay Protection

A valid webhook signature proves that someone holding the shared secret signed particular bytes. It does not prove the delivery is fresh, and it does not stop the same signed request from being sent twice. Secure receivers therefore need raw-body verification, a timestamp policy, and event-level deduplication.

The practical model

Treat webhook signature verification with replay protection as a system with four review points: verify exactly what was signed, enforce a bounded clock window, deduplicate durable events, rotate secrets without downtime. Give each point an owner, a measurable acceptance condition, and a rollback trigger. Thresholds should come from observed workloads, contracts, or risk tolerance rather than an unexplained default.

Verify exactly what was signed

Capture the raw request bytes before JSON parsing or middleware normalization. Construct the signed message exactly as the provider documents, often timestamp plus delimiter plus raw body. Compute HMAC with the configured algorithm and compare decoded bytes in constant time. Never compare attacker-controlled strings with an early-exit equality function.

Enforce a bounded clock window

Parse the signed timestamp as an integer and reject timestamps too far in the past or future. Five minutes is common, but the right value depends on delivery queues and clock discipline. Synchronize hosts and alert on clock drift; widening the window to hide time problems weakens replay resistance.

Deduplicate durable events

Store the provider event ID under a unique constraint before applying business effects. If processing and the business write share a database, use one transaction. If work moves to a queue, use an inbox table and idempotent consumer. Return a success response for an already-completed event so the provider stops retrying.

Rotate secrets without downtime

Accept the current and immediately previous secret for a short overlap. Identify which secret verified without logging either value. Expire the old key after confirmed propagation and retain metrics for verification failures by endpoint, not by sensitive payload.

Common pitfalls

  • Parsing JSON before signature verification.
  • Trusting an unsigned timestamp header.
  • Logging secrets or full sensitive payloads.
  • Returning success before durable enqueue or inbox insertion.

A focused test plan

  1. Verify exactly what was signed: verify that capture the raw request bytes before JSON parsing or middleware normalization. Then introduce the failure case “parsing json before signature verification” and confirm that the system fails visibly, stays within its safety boundary, and has a documented recovery action.
  1. Enforce a bounded clock window: verify that parse the signed timestamp as an integer and reject timestamps too far in the past or future. Then introduce the failure case “trusting an unsigned timestamp header” and confirm that the system fails visibly, stays within its safety boundary, and has a documented recovery action.
  1. Deduplicate durable events: verify that store the provider event ID under a unique constraint before applying business effects. Then introduce the failure case “logging secrets or full sensitive payloads” and confirm that the system fails visibly, stays within its safety boundary, and has a documented recovery action.
  1. Rotate secrets without downtime: verify that accept the current and immediately previous secret for a short overlap. Then introduce the failure case “returning success before durable enqueue or inbox insertion” and confirm that the system fails visibly, stays within its safety boundary, and has a documented recovery action.

Conclusion

Treat authenticity, freshness, and exactly-once business behavior as separate controls. Raw-byte HMAC, a strict time window, and durable deduplication together make webhook retries safe.

Advertisement