API Design

Webhook Signature Verification Explained

Learn how webhook signature verification works, including HMAC headers, raw request bodies, timestamps, replay protection, secret rotation, and safe failures.

Webhook endpoints are public URLs that receive event notifications from another system. Signature verification is how the receiver checks that an incoming webhook was sent by the expected provider and was not modified in transit.

Quick answer

Webhook signature verification usually works by computing an HMAC over the raw request body, timestamp, or signed payload with a shared secret. The receiver compares the computed signature with the signature header sent by the provider.

Verify before processing the event. Use the raw request body, check timestamps when provided, compare signatures safely, and make webhook processing idempotent because valid events may still be delivered more than once.

Why signatures matter

A webhook endpoint is often reachable by anyone who knows or guesses the URL.

Without verification, an attacker or broken client could send fake events:

POST /webhooks/billing
Content-Type: application/json
{
  "type": "invoice.paid",
  "data": {
    "accountId": "acct_123"
  }
}

If the backend trusts that JSON without verification, it may unlock features, mark invoices paid, send emails, or start background jobs based on untrusted input.

HMAC signature pattern

A common pattern uses an HMAC signature header:

POST /webhooks/billing
Content-Type: application/json
X-Webhook-Timestamp: 1790774400
X-Webhook-Signature: sha256=6f0...

The provider and receiver both know a secret. The provider computes a signature from the timestamp and raw body. The receiver computes the same signature and compares it with the header.

The exact signed payload depends on the provider. Some sign only the raw body. Others sign a string such as:

timestamp.raw_body

Follow the provider documentation exactly. A small difference in separators, encoding, or body parsing can make every valid signature fail.

Use the raw request body

Signature verification should use the raw bytes received by the server, not a parsed and reserialized JSON object.

This matters because JSON can be equivalent while the bytes differ:

{"id":"evt_123","type":"invoice.paid"}
{
  "id": "evt_123",
  "type": "invoice.paid"
}

Those bodies represent similar data, but their byte strings are different. If the provider signed the raw body, reformatting JSON before verification breaks the signature.

Use the JSON Formatter for local inspection, but do not format webhook bodies before verifying signatures in production code.

Timestamp and replay protection

Many providers include a timestamp in the signed payload. The receiver checks that the timestamp is within an accepted window, such as a few minutes.

This reduces replay risk. If an attacker captures a valid webhook request, they should not be able to resend it hours or days later.

Timestamp checks should account for normal clock drift. If your server clock is badly wrong, valid events may fail verification.

Use the Unix Timestamp Converter when debugging webhook timestamp examples and logs.

Constant-time comparison

Use constant-time comparison when comparing signatures if your runtime provides it.

Normal string comparison may return faster when the first characters differ. In high-risk systems, timing differences can leak information about the expected signature. Constant-time comparison reduces that risk.

Even with constant-time comparison, reject missing signatures, malformed headers, unsupported algorithms, and timestamps outside the accepted window.

Secret rotation

Webhook secrets should be rotatable.

A practical rotation plan may allow two active secrets for a short window:

  1. Add a new secret in the provider dashboard.
  2. Configure the receiver to accept both old and new secrets.
  3. Confirm new deliveries verify with the new secret.
  4. Remove the old secret after the migration window.

Store webhook secrets in server-side configuration or a secret manager. Never expose them in frontend JavaScript, static pages, public repos, analytics events, or logs.

Failure behavior

Invalid signatures should not be processed.

Common responses:

ScenarioPossible response
Missing signature401 Unauthorized or 400 Bad Request
Invalid signature401 Unauthorized or 403 Forbidden
Timestamp too old401 Unauthorized or 400 Bad Request
Valid event accepted200 OK or 204 No Content
Temporary internal failure after verification500 or 503

Use a response style that matches your provider’s retry rules. Read REST API Status Codes Explained for status code tradeoffs.

Idempotency after verification

A valid signature does not mean the event is new.

Webhook delivery is often at-least-once. The provider may retry the same event after a timeout, connection reset, or 5xx response.

After verification, store the event ID before performing side effects:

INSERT INTO processed_webhook_events (event_id, received_at)
VALUES (?, CURRENT_TIMESTAMP);

If the insert fails because the event was already processed, return success without repeating the side effect. Read Idempotency in APIs Explained for the broader duplicate-safe pattern.

Common mistakes

The first mistake is verifying a parsed JSON body instead of the raw body.

The second mistake is processing the event before verification. Validation should happen before side effects, database writes, emails, or queue publishing.

The third mistake is logging secrets or full signature headers. Logs should help debugging without exposing credentials.

The fourth mistake is assuming a valid signature prevents duplicate delivery. It proves origin and integrity, not uniqueness.

The fifth mistake is hard-coding one secret forever with no rotation path.

Read Webhooks Explained for delivery, retries, ordering, and receiver design. For duplicate-safe processing, read Idempotency in APIs Explained and Retry Patterns Explained. For API protection around credentials, read API Key Design Best Practices, Secret Management Explained, and Audit Logs Explained. For the full sequence, browse the API Design Learning Path, Backend Security Learning Path, and the Topics map.