- Creating a payment record (Order) before checkout.
- Verifying the payment is genuine after checkout.
- Setting up webhooks so your server knows when money arrives.
- Capturing the payment so it settles to your account.
How does the payment flow work?
- Create an order on your server (Step 1) and pass the
order_idto the browser. - Open Checkout with
checkout.js(Step 2). Razorpay handles the payment UI, OTP and redirects. - Verify the signature returned after payment on your server (Step 3).
- Capture and confirm the payment with webhooks (Step 4).
What are the payment states in Razorpay Checkout?
Every payment moves through a set of states. The state tells you exactly when to deliver goods or services and when to hold back.What do you need before you start?
Make sure these are in place before you write any code. Skipping one of them blocks you later.1. Razorpay Dashboard access
1. Razorpay Dashboard access
- Log in at dashboard.razorpay.com.
- Complete KYC and business verification. This is required before you can go live and accept real payments.
- Confirm the account is not restricted or under review.
2. Generate Test API Keys
2. Generate Test API Keys
- Go to Account & Settings → API Keys in the Dashboard.
- Make sure you are in Test Mode using the toggle at the top of the Dashboard.
- Select Generate Key and note your Key ID (
rzp_test_…) and Key Secret.
3. Technical prerequisites
3. Technical prerequisites
- A server-side backend (Node.js, Python, Java, PHP, Ruby, Go or .NET) to call the Orders API.
- HTTPS on your website. This is required for Live Mode.
- A valid, unexpired SSL/TLS certificate on your server and webhook endpoint.
- DNS propagated and resolving correctly for your webhook hostname.
Step 1: How do you create an order before the customer pays?
Before you show the checkout form, your server must create an Order on Razorpay using the Orders API. Think of it as a locked payment receipt. It records the amount and currency server-side so they cannot be changed by the time the customer pays. Keep these order rules in mind:- Orders are immutable. Once created, the amount and currency cannot be changed. If you need a different amount, create a new order.
- Orders do not auto-expire, and passing
expire_byis rejected by the API. - Control expiry by closing the checkout modal with the
timeoutoption.
Create an Order (server-side API call)
What does a Razorpay API error look like?
All Razorpay API endpoints return errors in the same structure. Thecode field identifies the error class, description gives a human-readable explanation and field, when present, identifies the parameter at fault.
order_id (for example, order_IluGWxBm9U8zJ8) linked to your internal order record. Also save the amount, currency and receipt for reconciliation. You will need the order_id in Step 2 and Step 3.- A customer can attempt payment multiple times against the same
order_id, for example, if their first card is declined and they then try UPI. - The
order.attemptsfield increments with each attempt. - You do not need to create a new order for a retry.
- Create a new order only if the fulfilment scenario changes, such as a different amount or a different customer.
Step 2: How do you show the payment form to your customer?
Once your server creates the order and sends theorder_id to the browser, load Razorpay’s checkout.js and open the payment modal. Razorpay handles the entire payment experience from here, including card entry, UPI, OTP and bank redirects.
Load the Razorpay script
Which approach should you use to open Checkout?
There are two ways to open Checkout. Choose one based on how your app is built.- Always prefill the customer’s phone number (
contact) with the country code, for example,+91XXXXXXXXXX. It pre-fills OTP fields and measurably reduces drop-off. rzp1.open()must be called directly from a user action such as a button click. Browsers block programmatic popup opens.
Checkout options reference
Can you integrate Standard Checkout on mobile apps?
Yes. Beyond the webcheckout.js, Standard Checkout is available for native and cross-platform apps. Order creation and signature verification stay on your server and are identical across web and mobile.
Android
iOS
React Native
Flutter
Step 3: How do you verify the payment is genuine?
When a payment succeeds, Razorpay returns three fields to the browser. Do not deliver goods or services yet. A malicious actor can forge these fields in the browser, so you must verify them on your server before you fulfil any order.Verify the signature on your server (mandatory)
The signature is an HMAC-SHA256 hash of theorder_id and payment_id, signed with your Key Secret. If it matches what Razorpay generated, the payment is authentic.
order_id from your own database, the razorpay_payment_id and the razorpay_signature.
Step 4: How do you know when you have actually been paid?
Signature verification confirms the payment is genuine, but a payment in theauthorized state is not yet in your account. You need to capture it, and you need a reliable way to know when captured status is confirmed, even if the customer closes their browser tab immediately after paying. Webhooks are Razorpay’s way of telling your server that a payment is done without relying on the browser.
Enable auto-capture (recommended for most merchants)
- Log in to Dashboard → Account & Settings → Payment Capture.
- Select Change next to Automatic Capture.
- Select Automatic Capture and set the time window. The default is immediate.
- Select Save.
- Capture settings only work if you have integrated the Orders API. Payments created without an
order_iddo not respect capture settings. - Capture settings on the Orders API take precedence over Dashboard settings. Override per order by passing
"capture": "automatic"or"capture": "manual"in the order creation request. - For manual capture, call the Capture Payment API (
POST /v1/payments/{payment_id}/capture) with the amount in paise before the capture window expires.
Set up webhooks
Configure webhooks so your server is notified the moment a payment completes, regardless of what happens in the customer’s browser.- Log in to Dashboard → Account & Settings → Webhooks.
- Select + Add New Webhook.
- Enter your publicly accessible HTTPS endpoint URL.
- Set a strong Webhook Secret of at least 32 random characters. You use this to validate incoming events.
- Subscribe to the minimum events below, then select Save.
Validate every incoming webhook
Every webhook payload from Razorpay is signed. You must verify theX-Razorpay-Signature header on each incoming request, or anyone could send fake events to your endpoint.
Razorpay provides a signature validation helper in every language SDK. Pass the raw request body, the X-Razorpay-Signature header value and your webhook secret to the helper for your stack:
200 immediately, then process the event in the background so a slow handler does not trigger Razorpay’s retries. The following Node.js (Express) pattern shows the full flow, including a replay-attack guard and idempotent processing:
How do you handle webhook edge cases?
Step 4.5: How do you handle Late Authorisation (Late Auth)?
Late Auth occurs when a payment is authorised by the bank after your order session has already expired on your side. For example, the customer starts payment, your order times out after 10 minutes, but the bank approves the transaction 12 minutes later. Without handling this, the customer has been debited but you have not fulfilled, which leads to a dispute. How Late Auth happens- The customer starts a payment (UPI, netbanking or card with OTP).
- Your server-side order expires, for example, you mark it abandoned after 10 minutes.
- The bank processes slowly and authorises the payment after your timeout.
- Razorpay sends a
payment.authorizedwebhook to your endpoint. - Your system does not recognise the order as active, and the customer is charged with no fulfilment.
Step 5: How do you test the integration before going live?
All testing is done in Test Mode using test API keys, so no real money moves. Each test scenario corresponds to a real situation that will cost you money or customer trust if it is not handled correctly.rzp_test_XXXXXXXXXXXXXXXX) in your code during testing.Test cards
International test cards
Test UPI and other methods
- A 4-digit OTP always succeeds (for example, 1234 or 0000).
- A 5-digit OTP starting with 1 always fails (for example, 12345).
- A 6 to 10 digit OTP always succeeds.
- For netbanking, select any bank and you will see Success and Failure buttons on the mock bank page.
- For wallets, any test amount works. Select Pay on the mock wallet page.
Test checklist
Run every scenario below in Test Mode before moving to go-live.Step 6: What is the go-live checklist?
All Tier 1 items must be verified before you switch to Live API keys. This checklist is your merchant audit record.Tier 1.1: Account and API Keys
Tier 1.1: Account and API Keys
- KYC documents submitted and approved on the Dashboard.
- Business category and website URL correctly set in the Dashboard.
- Live Mode API Keys generated from the Dashboard, not the test keys.
- Test API keys removed from all production environments and config files.
KEY_SECRETstored in environment variables, not hardcoded in source code.- Source code does not contain any API secrets, verified via
git grepor equivalent.
Tier 1.2: Integration correctness
Tier 1.2: Integration correctness
- Every payment attempt creates a fresh Order via the Orders API.
order_idis passed correctly to the Checkout options.- Amount in the Order API matches the amount in the Checkout options exactly, both in paise.
- Currency in the Order API matches the currency in the Checkout options.
checkout.jsloaded from the Razorpay CDN, not self-hosted.- Checkout opens only on a user action (button click), not auto-open on page load.
- Customer prefill (name, email, contact with country code) populated.
Tier 1.3: Security (non-negotiable)
Tier 1.3: Security (non-negotiable)
- Signature verification implemented server-side using HMAC-SHA256.
order_idused for verification comes from the server DB, not the client callback.- Order fulfilled only after signature verification passes.
- Timing-safe string comparison used (
timingSafeEqualorhmac.compare_digest). razorpay_payment_idstored in the DB for deduplication and idempotency.- API Key Secret never exposed to the frontend or logged in application logs.
- HTTPS enforced on all payment pages and callback or webhook URLs.
- SSL certificate valid and not expiring within 30 days.
Tier 1.4: Capture and webhooks
Tier 1.4: Capture and webhooks
- Auto-capture configured in the Dashboard, or the Orders API
captureparameter set. - All authorised payments will be captured within the capture window (confirmed).
- Goods or services not delivered before the payment reaches
capturedstate. - Settlement schedule confirmed with the merchant.
- Webhook URL configured in the Dashboard (Live Mode), not just Test Mode.
- Webhook secret configured and signature verified on every incoming event.
payment.capturedandpayment.failedevents subscribed and handled.refund.failedandpayment.dispute.createdsubscribed and handled.- Webhook endpoint returns HTTP 200 within 5 seconds. Heavy processing is async.
- Duplicate webhook events handled idempotently (
event.idstored and checked). - Replay attack guard: events older than 5 minutes are discarded.
- DNS resolves correctly for the webhook URL and the SSL cert is valid.
Tier 1.5: Go-live execution
Tier 1.5: Go-live execution
- All Test Mode scenarios from Step 5 pass with test keys.
- Live API keys generated from the Dashboard (Live Mode).
- Test API keys replaced with Live keys in all environments.
- Environment variables updated, not hardcoded keys.
- A live test transaction completed and verified in the Dashboard.
- Payment visible as Captured in the Live Dashboard.
- Webhook received and processed for the live test transaction.
- Rollback plan documented and tested (see the Appendix).
Tier 2.1: Operational hardening (within 30 days)
Tier 2.1: Operational hardening (within 30 days)
- Payment failure shown to the customer with a clear message and retry option.
- Checkout modal dismiss handled gracefully.
- Loading states shown while the Order is being created server-side.
- Double-submit prevention on the Pay button (disabled after first click).
- Mobile responsiveness of the payment page verified.
- Timeout handling implemented if the checkout session expires.
- Late Auth scenario handled (payment authorised after the order expires).
- Business logo set in the
imageparameter and theme colour matches brand guidelines.
Step 7: What should you monitor after go-live?
Your integration is live. Watch these metrics so you can act before small problems become customer-facing ones.- Check Dashboard → Payments for any payments stuck in the
authorizedstate. - Review webhook delivery logs for failed retries.
- Check for any
payment.dispute.createdevents and respond within 7 days. - Verify settlement amounts match expected totals.
- Monitor
refund.failedevents. Each requires manual follow-up.
Appendix
Rollback plan template
Fill this in before switching to Live keys. If something goes wrong, you need to act in seconds, not minutes.Logging strategy
Log enough to debug payments, but not so much that you create a PCI compliance risk.Common problems and what they mean
API endpoints reference
Related information
- Standard Checkout integration steps
- Orders API reference
- Set up webhooks
- Capture settings
- Error codes reference
- Troubleshooting and FAQs
Frequently asked questions
Do I need to create an order for every payment?
Do I need to create an order for every payment?
order_id, so you would never receive the money. You can reuse the same order_id if a customer retries after a failure, but create a new order if the amount or customer changes.Why must I verify the payment signature on the server?
Why must I verify the payment signature on the server?
razorpay_payment_id, razorpay_order_id and razorpay_signature) arrive in the browser, which you do not control. A malicious actor can forge them. Verifying the HMAC-SHA256 signature on your server with your Key Secret proves the payment is genuine before you fulfil the order. Skipping this is the most common cause of fraudulent orders.Why should I use webhooks if I already get a browser callback?
Why should I use webhooks if I already get a browser callback?
What is Late Auth and how do I handle it?
What is Late Auth and how do I handle it?
payment.authorized webhook: if the order is still active, fulfil normally; if it is expired, cancelled or already fulfilled by another payment, refund the customer immediately and notify them. See Step 4.5 for the handling pattern.How do I test the integration without real money?
How do I test the integration without real money?
rzp_test_…). Use the test cards and UPI VPAs in Step 5 to simulate success, failure, decline and pending scenarios. No real money moves in Test Mode. Run the full test checklist before switching to Live keys.My customer paid but the money never arrived. What went wrong?
My customer paid but the money never arrived. What went wrong?
order_id being passed to Checkout. Always create the Order first, pass the order_id and enable auto-capture. Check Dashboard → Payments for payments stuck in the authorized state.