Receive Razorpay Webhooks Locally (Test Razorpay Webhooks on localhost)

Test Razorpay webhooks locally on localhost without deploying. Inspect the real payment.captured payload, forward to your handler, and verify the X-Razorpay-Signature.

Receive Razorpay Webhooks Locally (Test Razorpay Webhooks on localhost)

You are integrating Razorpay — capturing payments, reconciling refunds, fulfilling orders — and you need to see your handler react to a real payment.captured or order.paid event. The problem is immediate: Razorpay will only POST to a public URL, and your handler is running on localhost:3000. Razorpay has no way to reach it.

The usual workarounds are slow. Deploying to a staging server for every code change kills your iteration speed. Copying a sample payload from the docs into curl gives you a guess at the real request, not the exact headers and raw body Razorpay actually sends — and getting the body byte-for-byte right matters a lot when you later verify the signature. What you want is to test Razorpay webhooks locally — real events, hitting your local handler, on a URL that does not change every time you restart.

This guide shows how to do exactly that.

Why testing Razorpay webhooks on localhost is tricky

A webhook is just an HTTP request Razorpay sends to a URL when something happens — a payment is captured, an order is paid, a refund is processed. Razorpay sits on the public internet; your dev machine usually does not. It is behind a router, a corporate firewall, or both, with no public IP and no inbound ports open.

So you need something in the middle: a public endpoint Razorpay can hit, that relays each request down to your laptop without you opening a single firewall port. That is what Webhook Relay does — and unlike a random tunnel URL, the endpoint is stable, so you configure Razorpay once and never touch it again.

Step 1: Inspect the real payload with Webhook Bin

Before you write any handler code, find out what Razorpay actually sends. Open the free Webhook Bin — no signup — and you get an instant public URL.

  1. Copy the Webhook Bin URL.
  2. In the Razorpay Dashboard, go to Account & Settings → Webhooks and click Add New Webhook.
  3. Paste the URL into Webhook URL, leave the Secret blank for now (you will add one in Step 3), and select the Active Events you care about — for example payment.captured, payment.failed, and order.paid.
  4. Save the webhook.

Razorpay lets you fire a test event from the Dashboard, or you can trigger a real one in Test Mode (a test payment, a refund). Either way the request lands in Webhook Bin right away, and you can inspect the full JSON body, the query string, and every header — including X-Razorpay-Signature, X-Razorpay-Event-Id, and the Content-Type: application/json.

Now you know the exact shape of the data before writing a line of code: the top-level event field (e.g. payment.captured), the payload.payment.entity object with its id, amount, currency, and status. For more on this approach, see How to test webhooks and What is a webhook.

Step 2: Forward the events to localhost with the relay agent

Once you know the payload, route those same events into your local handler. Sign up for Webhook Relay, install the relay agent (CLI or Docker), and create a bucket — say razorpay. The bucket gives you a stable public input endpoint.

Start forwarding to your local server:

relay forward --bucket razorpay http://localhost:3000/webhooks/razorpay

The agent opens an outbound connection to Webhook Relay and streams every incoming request down to http://localhost:3000/webhooks/razorpay. Because the connection is outbound, there are no firewall ports to open and no public IP needed — this works from your laptop, behind a corporate proxy, or inside a Kubernetes cluster. Running in Docker? The same command works in the official webhookrelay/webhookrelayd image. Full details are in the localhost forwarding docs.

Now update the webhook's Webhook URL in the Razorpay Dashboard to your Webhook Relay endpoint (or just create it there from the start). Trigger a test payment and watch it arrive on localhost.

Razorpay-specific configuration and quirks

A few Razorpay details worth knowing:

  • Where to add it: Account & Settings → Webhooks in the Dashboard. Test Mode and Live Mode each have their own webhooks — set one up in Test Mode for local development.
  • Active Events: pick exactly the events you handle (payment.captured, payment.failed, payment.authorized, order.paid, refund.processed, …). Branch on the top-level event field in your handler.
  • Raw body matters: Razorpay's signature is computed over the raw request body. If your framework parses JSON before you can read the bytes, capture the raw body first — re-serialized JSON will not match.
  • Duplicate deliveries: Razorpay may resend an event. Use the X-Razorpay-Event-Id header (unique per event) to make your handler idempotent.
  • Order of events: payment.authorized can arrive before payment.captured for the same payment, so design your handler to tolerate out-of-order and repeated events.

Step 3: Verify the Razorpay webhook signature

Set a Secret on the webhook and Razorpay will sign every request. It computes an HMAC-SHA256 of the raw request body using your webhook secret and sends the hex digest in the X-Razorpay-Signature header. Your handler should recompute the HMAC over the raw body with the same secret and compare in constant time before trusting the payload — a request that does not match should be rejected outright.

To sanity-check your implementation, paste a captured body, your secret, and the received signature into the free HMAC signature verifier. For language-specific code and the common pitfalls (reading the body after a JSON parser has consumed it, timing-safe comparison), read Verify a webhook signature.

Replay and iterate

This is where local development gets fast:

  • Resend from Razorpay — the Dashboard lets you fire test events at your webhook on demand.
  • Replay from Webhook Relay — past requests are stored on your bucket, so you can resend a captured payment.captured event without touching Razorpay at all. The raw body and X-Razorpay-Signature are preserved, so your signature check runs against the real thing.
  • Iterate on your handler by editing code and replaying the same delivery until it behaves correctly. No redeploys just to test a refund path.

Because the Webhook Relay endpoint is stable, you can stop and restart the agent, reboot your machine, or come back next week — the Razorpay configuration never needs to change. If you are also wiring up another payment provider, the same flow works for Stripe webhooks on localhost.

Get started

  1. Inspect the real payload in the free Webhook Bin — no signup needed.
  2. Create a Webhook Relay account, install the agent, and run relay forward --bucket razorpay http://localhost:3000/webhooks/razorpay.
  3. Point your Razorpay webhook at the stable endpoint, trigger a test payment, and watch it hit localhost.

You will be testing real Razorpay events against your local handler in a few minutes — no deploys, no open firewall ports, and a URL you configure exactly once.