---
title: "Receive Razorpay Webhooks Locally | WebhookRelay"
meta:
  "og:description": "Test Razorpay webhooks locally on localhost without deploying. Inspect the real payment.captured payload, forward to your handler, and verify the X-Razorpay-Signature."
  "og:title": "Receive Razorpay Webhooks Locally"
  description: "Test Razorpay webhooks locally on localhost without deploying. Inspect the real payment.captured payload, forward to your handler, and verify the X-Razorpay-Signature."
---

![Stripes](https://webhookrelay.com/blog/receive-razorpay-webhooks-locally/images/stripes.svg)

# **Receive Razorpay Webhooks Locally**

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)](https://webhookrelay.com/blog/receive-razorpay-webhooks-locally/images/blog/heroes/localhost.jpg)

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](#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](https://my.webhookrelay.com/register) 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](#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](https://webhookrelay.com/blog/receive-razorpay-webhooks-locally/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](https://webhookrelay.com/blog/receive-razorpay-webhooks-locally/blog/how-to-test-webhooks/) and [What is a webhook](https://webhookrelay.com/blog/receive-razorpay-webhooks-locally/blog/what-is-webhook/).

## [Step 2: Forward the events to localhost with the relay agent](#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](https://my.webhookrelay.com/register), 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](https://webhookrelay.com/blog/receive-razorpay-webhooks-locally/docs/webhooks/internal/localhost/).

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](#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](#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](https://webhookrelay.com/blog/receive-razorpay-webhooks-locally/hmac-verification/). 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](https://webhookrelay.com/blog/receive-razorpay-webhooks-locally/blog/verify-webhook-signature/).

## [Replay and iterate](#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](https://webhookrelay.com/blog/receive-razorpay-webhooks-locally/blog/receiving-stripe-webhooks-localhost/).

## [Get started](#get-started)

1. Inspect the real payload in the free [Webhook Bin](https://webhookrelay.com/blog/receive-razorpay-webhooks-locally/webhook-bin/) — no signup needed.
2. [Create a Webhook Relay account](https://my.webhookrelay.com/register), 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.