Razorpay trips people up in a way most providers do not: it has two separate signatures that use the same algorithm but different keys and different inputs, and they are easy to confuse. One verifies webhooks. The other verifies a checkout payment on your success handler. Sign the wrong thing with the wrong secret and your verification fails while your code looks correct. This post shows the exact Razorpay webhook verification, the raw-body trap, and how the webhook signature differs from the payment signature.
First: Razorpay has two different signatures
Both are HMAC-SHA256 and both end up as a hex string, which is exactly why they get mixed up. But they are not interchangeable:
Webhook signature arrives in the X-Razorpay-Signature header on webhook requests. It is keyed with your webhook secret and computed over the raw request body.
Payment signature comes back as razorpay_signature in the Checkout success handler, alongside razorpay_order_id and razorpay_payment_id. It is keyed with your API key secret and computed over order_id + "|" + payment_id.






