</> Payments Toolbox

Webhook signature verifier

Check a Razorpay, Stripe, Cashfree or PayU signature against your secret — webhooks, and Razorpay's checkout and payment-link schemes — and see exactly which string was signed.

Runs in your browser — nothing you paste is sent to a server

Razorpay signs with HMAC-SHA256 over the raw body, hex encoded, and sends it in the X-Razorpay-Signature header. Provider docs

Paste the body exactly as received, before any JSON parsing. Re-indenting or re-serialising it will change the signature.

The secret you set when creating the webhook in the Razorpay dashboard — not your API key secret.

What gets signed, by provider

  • RazorpayHMAC-SHA256 over the raw body, hex encoded, sent in X-Razorpay-Signature.
  • Razorpay Checkout / LinksHMAC-SHA256 over pipe-joined ids, hex encoded, keyed with the API key secret, sent in razorpay_signature (returned with the payment, not a header).
  • StripeHMAC-SHA256 over `timestamp.body`, hex encoded, sent in Stripe-Signature.
  • CashfreeHMAC-SHA256 over `timestamp + body`, base64 encoded, sent in x-webhook-signature.
  • PayUSHA-512 over a pipe-delimited field string (reverse hash), sent in hash (a field inside the posted body).

Worked example

Each example below is a real payload with a signature generated by this page's own verification code. Paste any of them into the tool above to watch it pass, then change a single character of the body to watch it fail.

RazorpayHMAC-SHA256 over the raw body, hex encoded

Raw body
{"entity":"event","account_id":"acc_BFQ7uQEaa7j2z7","event":"payment.captured","contains":["payment"],"created_at":1739510400}
Webhook secret
whsec_razorpay_demo_secret
X-Razorpay-Signature
397bb1d09334c5158b5bc4f63c5a9f3537970bad6755f50a6d2766dc483f348a

Signature is valid. The body was signed with this secret and has not been altered in transit.

Razorpay Checkout / LinksHMAC-SHA256 over pipe-joined ids, hex encoded, keyed with the API key secret

Raw body
{"razorpay_order_id":"order_Deadbeef123","razorpay_payment_id":"pay_Cafebabe456"}
API key secret
rzp_test_demo_api_key_secret
razorpay_signature (returned with the payment, not a header)
2c142deebcf1292d150f23d94adcb23b2a6c17a7bb5b0e839f8a6cb8fda6251c

Signature is valid. The body was signed with this secret and has not been altered in transit.

StripeHMAC-SHA256 over `timestamp.body`, hex encoded

Raw body
{"id":"evt_1PdemoStripeEvent","object":"event","type":"payment_intent.succeeded","created":1739510400}
Endpoint signing secret
whsec_stripe_demo_secret
Stripe-Signature
t=1739510400,v1=f4d187e77dd38e631ced90b19ab6b809a947801ee7e24f98776480e720a8862d

Signature is valid and the timestamp is inside the replay window.

CashfreeHMAC-SHA256 over `timestamp + body`, base64 encoded

Raw body
{"type":"PAYMENT_SUCCESS_WEBHOOK","data":{"order":{"order_id":"order_demo_001","order_amount":499.00,"order_currency":"INR"}}}
Client secret
cfsk_ma_demo_client_secret
timestamp
1739510400
x-webhook-signature
FOnlgr0yZHNVBJOoMXiVu7OO7/wJ3YnNd6Rs/iWCaGs=

Signature is valid. The body was signed with this secret and has not been altered in transit.

PayUSHA-512 over a pipe-delimited field string (reverse hash)

Raw body
key=gtKFFx&txnid=demo_txn_001&amount=499.00&productinfo=Pro%20plan&firstname=Asha&email=asha@example.com&udf1=&udf2=&udf3=&udf4=&udf5=&status=success
Merchant salt
eCwWELxi
hash (a field inside the posted body)
deb1f8406bedab59a44365988240aeeb220737b2fca2588754eb0516b1d76df7266fb6d246ed56a5bfb0a904322a72ca0e4634ceb6e8f2e665434ac812e11659

Signature is valid. The body was signed with this secret and has not been altered in transit.

Frequently asked questions

Why does my signature fail even though the secret is right?
Almost always because the body is not byte-identical to what the provider signed. Frameworks that parse JSON and re-serialise it change key order, spacing and number formatting, and the signature is over the exact bytes. Capture the raw body before any parsing middleware touches it — express.raw(), request.body in Next.js route handlers, or the equivalent — and verify against that.
Is it safe to paste my webhook secret here?
The verification runs entirely in your browser using the Web Crypto API. The payload, the secret and the signature are never sent to a server, and there is no analytics call carrying them. You can confirm this by opening your browser's network tab while you verify, or by disconnecting from the network — the tool keeps working.
What is the difference between the signature schemes?
Razorpay signs the raw body with HMAC-SHA256 and hex encodes it. Stripe signs the timestamp and body joined by a dot, hex encoded, and also enforces a five minute replay window. Cashfree signs the timestamp concatenated directly onto the body with no separator, base64 encoded. PayU is not an HMAC scheme at all — it is a plain SHA-512 over a pipe-delimited field string that includes your salt.
Why does razorpay_signature fail when my webhook signatures pass?
Because they are two different schemes and only one of them uses the webhook secret. A webhook is HMAC-SHA256 over the raw request body, keyed with the secret you set on the webhook. The razorpay_signature returned by Checkout, a Payment Link or a subscription is HMAC-SHA256 over a pipe-joined list of ids, keyed with your API key secret — the one paired with your rzp_live_ or rzp_test_ key id. Using the webhook secret for the second one is the most common cause of this failure. The strings also differ per flow: an order signs order_id|payment_id, a payment link signs payment_link_id|payment_link_reference_id|payment_link_status|payment_id, and a subscription signs payment_id|subscription_id — note the subscription puts the payment id first, which is genuinely inconsistent and catches people out.
My PayU hash matches on some transactions but not others. Why?
Almost certainly additionalCharges. PayU has two reverse-hash formulas and picks between them based on whether that field is present in the response: with it, the charge value is prefixed to the whole hash string; without it, the generic formula applies. So a merchant who collects additional charges on only some transactions is verifying two different formulas coming from one endpoint, which looks like an intermittent, unreproducible failure. This tool detects the field in your payload and tells you which variant it used.
Can I verify signatures automatically instead of by hand?
Not yet. The verification you see here is a pure function with no browser dependencies, so exposing it as an API you can call from your own code or CI is planned, as is a persistent capture endpoint that receives real webhooks, checks the signature on arrival and lets you replay them at your service. Neither is available today, and the tool on this page is free and unlimited either way.