Verify Webhook Signatures with HMAC

Learn the standard pattern for verifying webhook signatures using HMAC. Understand header inspection, payload hashing, and constant-time comparison to prevent forgery.

API Security

Detailed Explanation

Webhook Signature Verification with HMAC

Webhooks allow services to send real-time notifications to your application via HTTP POST requests. However, without verification, anyone could send a fake webhook to your endpoint. HMAC-based signature verification solves this by ensuring only the legitimate service could have generated the request.

How Webhook Signing Works

The typical webhook signing flow works as follows:

  1. Shared secret: When you register a webhook endpoint, the service provides a secret key (or you generate one)
  2. Request signing: When the service sends a webhook, it computes HMAC(secret, request_body) and includes the signature in an HTTP header
  3. Verification: Your server receives the request, computes the same HMAC over the raw request body using the shared secret, and compares it to the signature header

Implementation Pattern

# Pseudocode for webhook verification
received_signature = request.headers["X-Signature"]
computed_signature = HMAC_SHA256(secret_key, raw_request_body)
if constant_time_equal(received_signature, computed_signature):
    process_webhook(request)
else:
    return 403 Forbidden

Critical Implementation Details

Use the raw body: You must compute the HMAC over the exact bytes received, not a parsed-and-reserialized JSON object. JSON serialization is not deterministic — different libraries may order keys differently or handle whitespace differently.

Constant-time comparison: Always use a timing-safe comparison function. Standard string comparison (===) returns early on the first mismatched character, leaking information about how many prefix bytes are correct. This can be exploited in a timing attack.

Signature format: Most services send signatures in one of these formats:

  • Raw hex: sha256=a3f2b8...
  • Base64: v1=o/K4wQ...
  • Prefixed with algorithm: t=1614556800,v1=...

Replay Protection

Some services include a timestamp in the signed payload or a separate header. You should:

  1. Verify the signature first
  2. Check that the timestamp is within an acceptable window (e.g., 5 minutes)
  3. Optionally deduplicate by webhook ID

This prevents an attacker from replaying old, valid webhooks.

Use Case

Webhook signature verification is mandatory when receiving payment notifications from Stripe, deployment events from GitHub, message callbacks from Twilio, or any third-party service that pushes data to your application endpoint.

Try It — HMAC Generator

Open full tool