keepcalculator

06 Numbers and Powers

Webhook Security: Signatures, Timestamps & Replay Protection

Card No. 06·62900 · Updated · Standard formula, shown below

Webhook security relies on three core mechanisms: signatures to verify the sender, timestamps to bound the message’s age, and replay protection to reject duplicate deliveries. This article explains how each works, how to implement them correctly, and common pitfalls to avoid.

Why Webhooks Need Authentication

Webhooks are HTTP callbacks that notify your server when an event occurs (e.g., a payment, a new user, a file upload). Unlike traditional APIs where your server initiates the request, webhooks are delivered by a third-party service to an endpoint you control. This means your endpoint is publicly exposed and accepts POST requests from anyone who knows the URL. Without verification, an attacker can send fake events, inject malicious data, or trigger unintended actions.

Signatures solve the authentication problem by proving that the payload genuinely came from the expected sender and was not tampered with during transit. Timestamps and replay protection address the freshness and uniqueness of the message, preventing an attacker from capturing a legitimate request and resending it later.

How Signatures Work

The most common method is HMAC (Hash-Based Message Authentication Code). The provider computes a signature by hashing a secret key (shared only between the sender and receiver) together with the request payload. The signature is usually sent in a header like X-Signature or X-Hub-Signature-256.

To verify, your server recomputes the HMAC using the same secret and compares it to the received signature. If they match, the payload is authentic. Always use a constant-time comparison function (e.g., crypto.timingSafeEqual in Node.js) to prevent timing attacks that could leak the secret.

Some providers include the timestamp as part of the signed content (e.g., v1:timestamp:payload). This ties the signature to a specific time, making it impossible for an attacker to alter the timestamp without invalidating the signature.

Timestamps: Why They Matter

A timestamp in the webhook header tells you when the event was sent. Even if the signature is valid, an attacker could capture a legitimate request and replay it hours or days later. By checking the timestamp, you can reject requests that are older than a certain threshold (commonly 5 minutes).

However, timestamps alone are not enough. An attacker can simply change the timestamp to a current value and recompute the signature—but only if they know the secret. Since they don’t, the signature check will fail. Therefore, timestamps work in conjunction with signatures: the signature covers the timestamp, so any modification breaks verification.

Replay Protection: Beyond Timestamps

Timestamps limit the window for replay, but within that window an attacker could still resend a captured request. For example, if a webhook triggers a payment or account deletion, a replay within 5 minutes could cause duplicate actions. To fully prevent this, you need to track unique message IDs.

Most providers include a unique event ID (e.g., X-Event-ID or X-Request-ID). Your receiver should store processed IDs in a cache (e.g., Redis) with a TTL equal to or slightly longer than the timestamp tolerance. When a request arrives, check if the ID has already been processed; if so, return 200 OK without executing the action again.

This combination—signature verification, timestamp freshness, and ID deduplication—gives you robust replay protection.

Common Implementation Mistakes

  • Using a plain hash instead of HMAC: A simple SHA-256 of the payload is not secure because an attacker can compute it without the secret. Always use HMAC with a shared secret.
  • Not verifying the timestamp before the signature: Always check the timestamp first; if it’s too old, reject immediately. This saves CPU and prevents replay of old messages.
  • Using string concatenation incorrectly: Some providers sign the raw body, others sign a JSON string. Make sure you use the exact bytes that were sent. If the provider signs the raw body, don’t parse and re-serialize it before verification.
  • Storing the secret in code: Use environment variables or a secrets manager. Never hardcode secrets in your repository.
  • Ignoring the signature header format: Some providers send multiple signatures (e.g., v1=abc, v2=def). Parse and compare all supported versions, but reject if none match.

Real-World Example: Stripe and GitHub

Stripe’s webhook signatures use an HMAC-SHA256 of the timestamp plus the payload, sent in a Stripe-Signature header. They recommend checking the timestamp to be within 5 minutes and using constant-time comparison. GitHub uses a similar approach with X-Hub-Signature-256 and a secret you configure. Both provide libraries that handle verification for you, but understanding the underlying mechanics helps you debug issues.

When implementing your own, follow the same pattern: include a timestamp in the signed content, use a unique event ID, and document the exact signing algorithm so receivers can verify correctly.

Choosing the Right Approach

For most webhook consumers, the following checklist is sufficient:

  1. Check the timestamp is within 5 minutes of your server time.
  2. Recompute the HMAC using the shared secret and the raw body (and timestamp if included).
  3. Compare signatures using a constant-time function.
  4. Check for duplicate event IDs in your deduplication store.
  5. If any check fails, return 400 or 403 and do not process the event.

For providers, you should sign the timestamp and payload together, include a unique event ID, and document the algorithm. Consider using a versioned signature scheme to allow future changes without breaking existing consumers.

Frequently Asked Questions

What if my webhook provider doesn’t send a timestamp?

If the provider doesn’t include a timestamp, you cannot implement time-based replay protection. You can still use event IDs for deduplication, but an attacker could replay an old event with a valid signature. In that case, you should contact the provider to request timestamp support or use a different delivery mechanism.

Can I use HTTPS alone for security?

HTTPS encrypts the connection, but it does not authenticate the sender. An attacker could still send requests to your endpoint from a different server. Signatures are necessary to ensure the message came from the expected provider.

How do I handle clock skew between my server and the provider?

Allow a small tolerance (e.g., 5 minutes) to account for network delays and server clock differences. If your server’s clock is significantly off, use NTP to sync it.

What if the signature verification fails?

Log the failure with the event ID and timestamp, but do not process the event. Return a 400 or 403 response. You may want to alert your team if failures are frequent, as it could indicate a misconfiguration or an attack.

Should I use a different secret for each provider?

Yes, use a unique secret per provider. If one secret is compromised, you can rotate it without affecting other integrations. Store secrets in a secure vault and rotate them periodically.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →