Generate and Verify Webhook Signatures

Paste the exact raw payload and signing secret to generate or verify an HMAC signature. GitHub and Stripe-style presets are included; a matching signature does not check timestamp freshness or prevent replay.

Runs locally in your browser
Signature inputs
Pasted line breaks become LF in this field. Turn this on to reproduce a body that was signed with CRLF bytes.
Generated signature
Enter a secret and payload to generate a signature.
Verify a received signature
Generate a signature or paste an incoming signature to verify it locally.
Header preview
Generate a signature to see the corresponding request header.
Server-side verification patterns
Generate a signature to see Node.js and PHP verification patterns.

Sign the exact bytes you received

Webhook checks fail when middleware parses or reformats the body before verification. Keep the raw body, apply the provider's documented signing input, and use a timing-safe comparison in your server code. This page never sends the payload or secret anywhere.

How to generate or verify a webhook signature

Sign the exact payload you paste with HMAC-SHA-256, SHA-384 or SHA-512, reproduce GitHub's X-Hub-Signature-256 and Stripe's v1 header formats, and compare a received signature byte by byte with the same secret.

Hashing happens in this tab through Web Crypto. The payload, the secret and the signatures stay in the browser; a match confirms the bytes, not the freshness of the event.

  1. Choose the signature format: generic HMAC, GitHub X-Hub-Signature-256 or Stripe v1. The two provider formats always use SHA-256.
  2. Paste the exact raw payload and the signing secret. For Stripe, press Use current time or type the timestamp that was sent; tick Sign with CRLF line endings if the sender signed CRLF bytes.
  3. Press Generate signature to see the matching request header and the Node.js and PHP verification snippets. Editing any input afterwards dims the value until you generate again.
  4. To check a received value, paste it into Verify a received signature and press Verify signature. Hex works everywhere, base64 works for generic webhooks, and a full Stripe-Signature header is read together with its t= timestamp.
  5. Use Copy signature for the header value, and Clear to empty every field and reset the format to generic HMAC.

What the generated output contains

Signing input for each format

Generic HMAC signs the raw request body. GitHub signs that same raw body and sends sha256=<hex digest> in X-Hub-Signature-256. Stripe signs the string <timestamp>.<raw body> and sends t=<timestamp>,v1=<hex digest> in Stripe-Signature.

When a Stripe header is pasted for verification, the t inside the header builds the signing input; the timestamp field is only read when the header carries no t=. A rotated secret can produce several v1 values, and the check passes when any of them matches.

Why an identical body can still fail

A browser textarea turns pasted CR and CRLF into LF. If the sender signed CRLF bytes, enable Sign with CRLF line endings: every LF is rebuilt as CRLF before hashing.

Text differences matter: a re-serialized JSON body, a stripped trailing newline, a changed space, a decoded URL component, or reading the parsed body instead of the raw bytes all produce a different digest. Copying only the hex part of a provider header drops the sha256= or v1= prefix the format expects, although the tool accepts both forms.

What the page does not check

Verification compares HMAC bytes only. It does not reject old timestamps or replayed events, does not know the provider's tolerance window, and never contacts the sender.

Base64 is accepted for generic webhooks; GitHub and Stripe transmit hex, so no base64 verification is offered for them. Rotating or revoking a leaked secret stays a server-side task.

Recent tools: