Available in🇮🇳 India
Webhooks are your source of truth; fetch APIs are your safety net. Build one state machine that both feed.
Test it: subscribe to the events your method pages list. Verify the X-Razorpay-Signature header on every webhook, store event IDs and skip repeats and handle out-of-order and duplicate events. Confirm your reconciler recovers a missed webhook by calling GET /v1/orders/:id/payments.
Which events to subscribe to
Bare-minimum events
Recommended events
Handle every event correctly
- Reply
2xxwithin 5 seconds, then process the event in the background. - Check that the webhook came from Razorpay: recompute
X-Razorpay-Signatureover the raw body before parsing. - Ignore duplicates using the
x-razorpay-event-idheader. - Do not rely on the order of events. Never move an order back from
paid.
Webhook or fetch?
Design principles: one state machine, two inputs — webhooks and fetches update the same order record through the same code. Forward-only — ignore any update that would move a payment or order back; accept
failed → authorized as a late authorisation. Back off — fetch a pending order a few times with growing gaps, then leave it to the reconciler. Idempotent — processing the same update twice changes nothing.
APIs you’ll use
Scenarios to test your integration
Scenario details
BP-01 — Fulfil on the webhook, not the callback Before you start: webhook subscribed toorder.paid (or payment.captured) and payment.failed.
How to test: create an order and pay it with a success trigger. Do not run your callback handler (as if the customer closed the browser). Wait for the webhook.
Expected result: the order is fulfilled once, from the webhook (payment.captured, order.paid).
Your integration should: treat the callback as a hint — verify the signature and show a result, but fulfil only from the webhook or a fetch.
BP-03 · BP-10 — Duplicate and out-of-order webhooks
Before you start: a stored order.paid and an earlier payment.failed for the same order.
How to test: replay order.paid with the same x-razorpay-event-id. Replay the earlier payment.failed after order.paid.
Expected result: nothing changes — the duplicate is ignored and the order stays paid.
Your integration should: key processing on x-razorpay-event-id; move states forward only.
BP-04 — Endpoint down, then recovery
Before you start: a reconciler you can run on demand.
How to test: make your endpoint return 500. Pay two orders: one success, one failure. Run the reconciler. Restore the endpoint and let retries arrive.
Expected result: both orders reach the right state from the reconciler; later retries are ignored.
Your integration should: after a real outage, re-enable the webhook on the Dashboard and fetch every order from the outage window.
Live mode differs: Razorpay retries for 24 hours, then disables the webhook.
BP-05 · BP-20 — Fetch fallback and the reconciler
Before you start: a reconciler you can run on demand; no webhook subscription for the success event.
How to test: pay one order with a success trigger. Leave a second order unpaid; fail a payment on a third. Run the reconciler: it fetches GET /v1/orders/:id/payments for orders still created/attempted after 15 minutes.
Expected result: the paid order is fulfilled; the other two stay open, or are closed by your own timeout.
Your integration should: back off between fetches and never poll in a tight loop.
Webhooks: What Fires, and What It Lets You Decide
Every scenario above delivers a specific set of events, in a specific order. This table is the one place that says which, and — the part that actually matters — what each one entitles you to do. Most integration bugs that reach production are not missing webhooks; they are acting on the wrong one.
Before you trust any of them
- Verify the signature on every event. Replay one event to your endpoint and send one with a changed body. The replay must be ignored and the tampered event rejected. An endpoint that skips this is an open write to your order state.
- Expect duplicates. The same event can arrive more than once. Key your handler on the event ID and make it idempotent.
- Order is not guaranteed across events. Do not assume
payment.capturedlands beforeorder.paid; handle whichever arrives first. - Webhook first, callback second. The browser callback can be lost, blocked or forged. Fulfil on the webhook.
Common integration gaps to avoid
Simulate failures
Available in test mode
Not available in test mode