Razorpay Webhook: Example Payload, Headers and Signature

Razorpay wraps each event's entities inside payload under their own names, and signs the body with a secret you choose when creating the webhook. Here is a captured payment and how to validate it.

No webhook URL yet. Creating one needs no account.

Request details
Requests sent to your webhook URL appear here: method, headers and the body exactly as it arrived.

The button posts the example below to your webhook URL from your browser. It is not sent by Razorpay, it carries your browser's User-Agent, and its signature is a placeholder that will not verify. To see a real delivery, follow the steps further down.

Last updated . How this page is checked.

How the webhook URL works. It is a public address that records whatever is sent to it and answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is deleted 24 hours after it was created — or sooner if you press Delete URL or our server restarts, because requests are held in memory and never written to disk.

Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.

What is a Razorpay webhook?

A Razorpay webhook is an HTTP POST sent to a URL configured in the dashboard when a subscribed event occurs: a payment is authorised, captured or fails, an order is paid, a refund is processed, a subscription is charged, a payout changes state. Because many payment methods complete outside the browser, the webhook is the dependable confirmation that a payment succeeded.

The envelope names the event in event and lists the entity types it carries in contains. Each of those appears under payload as an object with a single entity member, so a payment is found at payload.payment.entity. An order.paid event contains both an order and the payment that paid it.

What a Razorpay webhook request looks like

The request line and headers as they reach your server, followed by the body. Ids, tokens and the signature are placeholders.

POST /your/webhook/path HTTP/1.1
Host: your-server.example
X-Razorpay-Signature: 0f0c1e5a4d3b2c1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f
X-Razorpay-Event-Id: OEXAMPLE0event1
Content-Type: application/json
User-Agent: Razorpay-Webhook/v1
{
  "entity": "event",
  "account_id": "acc_EXAMPLE0000001",
  "event": "payment.captured",
  "contains": [
    "payment"
  ],
  "payload": {
    "payment": {
      "entity": {
        "id": "pay_EXAMPLE0000001",
        "entity": "payment",
        "amount": 50000,
        "currency": "INR",
        "status": "captured",
        "order_id": "order_EXAMPLE000001",
        "method": "upi",
        "captured": true,
        "email": "ada@example.com",
        "contact": "+919876543210",
        "notes": {
          "order_ref": "6735"
        },
        "fee": 1180,
        "tax": 180,
        "created_at": 1767225590
      }
    }
  },
  "created_at": 1767225600
}

Amounts are integers in the currency's smallest unit, so 50000 with INR is 500.00 rupees. notes returns the key-value pairs you attached when creating the order or payment and is the simplest link back to your own records. Timestamps are Unix seconds. The word entity appears at three levels with three meanings: the envelope's type, the wrapper key, and the payment's own type.

Headers Razorpay sends

HeaderExampleWhat it is
X-Razorpay-Signature0f0c1e5a4d3b2c1a9e8d7c6b5a4f3e2d1c0b9a8f7e6…HMAC-SHA256 of the body, hex, keyed with the webhook secret.
X-Razorpay-Event-IdOEXAMPLE0event1Unique id of the event. It is the same on every retry.
Content-Typeapplication/jsonThe body is JSON.
User-AgentRazorpay-Webhook/v1Identifies the sender.

Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.

How Razorpay signs a webhook

When you create a webhook you set a secret for it. Razorpay computes an HMAC-SHA256 of the raw request body with that secret and sends the hex digest in X-Razorpay-Signature. The webhook secret is unrelated to your API key secret, and using the wrong one of the two is the usual cause of a mismatch.

No timestamp is signed, so the signature alone does not stop a replay; record X-Razorpay-Event-Id and ignore an id you have already handled. Compute it over the raw request body exactly as received — before any JSON parsing — and compare with a constant-time function.

const crypto = require('crypto');

// rawBody: Buffer or string of the body exactly as received
function verifyRazorpay(rawBody, header, webhookSecret) {
  const expected = crypto.createHmac('sha256', webhookSecret)
    .update(rawBody).digest('hex');
  const a = Buffer.from(header || '');
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Timeouts, retries and event order

Return a 2xx within a few seconds; Razorpay's documentation at the time of writing gives five. Anything else is a failure, and the event is sent again with exponential backoff for about a day. A webhook that is still failing after that period is disabled and has to be re-enabled in the dashboard.

The same event can therefore arrive several times, always with the same X-Razorpay-Event-Id. Different events for one payment can also arrive out of sequence, with payment.captured ahead of payment.authorized; decide the outcome from the payment's status, never from which event came last.

Catch a real Razorpay webhook

  1. Press Create webhook URL at the top of this page and copy the address.
  2. In the Razorpay Dashboard, switch to Test Mode and open the Webhooks section of the account settings.
  3. Add a webhook, paste the address as the URL, type a throwaway secret, and tick events such as payment.captured.
  4. Make a test payment through a test-mode checkout or payment link.
  5. Read the event, the signature and the event id header in the inbox above.

To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.

Things that catch people out

  • Two different secrets. The webhook secret you typed is what signs webhooks. The API key secret signs the checkout response instead.
  • Authorised is not captured. Without automatic capture, an authorised payment that is never captured is refunded later; fulfil on payment.captured or order.paid.
  • Test and live mode have separate webhooks. One configured in test mode never fires for live payments.

api-tester.com is independent and is not affiliated with Razorpay. Providers change header names, limits and retry schedules; treat the official Razorpay documentation as the source of truth.

Related examples and tools

Other webhooks signed with an HMAC of the body

References

These links leave api-tester.com, which is independent of the companies and projects they point to.

Razorpay webhook FAQ

What does a Razorpay webhook payload look like?

A JSON object with entity set to event, account_id, the event name, a contains array, a payload object holding each entity under its type name with an entity member, and created_at.

How do I verify X-Razorpay-Signature?

Compute an HMAC-SHA256 of the raw request body using the webhook secret you set in the dashboard, hex-encode it and compare it with the X-Razorpay-Signature header in constant time.

How do I avoid processing a Razorpay webhook twice?

Store the value of the X-Razorpay-Event-Id header for each processed event and skip any request whose id you have already seen.

Does Razorpay retry failed webhooks?

Yes. A non-2xx response or a timeout is retried with exponential backoff for about a day according to Razorpay's documentation at the time of writing, after which the webhook is disabled.

Which Razorpay event means the payment succeeded?

payment.captured for a payment, or order.paid for an order. payment.authorized only means the funds are held and still need capturing.