Most payment integrations fail for the same three reasons: tutorials mix the App Router with the Pages Router, the key_secret leaks into client code, and webhook signatures are never verified. This guide shows you how to integrate Razorpay in Next.js the way we do it at VoiceAct Solutions for production e-commerce builds — a server-side order route, a checkout page that never touches your secret, and signature-verified callbacks. When you are done, you will accept UPI, cards and net banking in under an hour.
How to Integrate Razorpay in Next.js: The Architecture
The one rule that keeps payments safe: the browser may know your key_id, but only the server may know key_secret. Razorpay's flow has two halves — your backend creates an order, and Checkout.js handles the customer-facing payment. In code, that looks like:
- The client calls your
/api/razorpay/create-orderroute with the amount and a receipt id. - The route creates an order via the Razorpay REST API and returns the
order_id. - Checkout.js opens, collects UPI, card or net-banking details, and hands back a
payment_id. - Your server verifies the signature before marking the order paid.
Step 1: Install and Set Up Credentials
npm install razorpay
Then in .env.local:
RAZORPAY_KEY_ID=rzp_live_xxxxxxxx
RAZORPAY_KEY_SECRET=xxxxxxxxxxxxxxxx
NEXT_PUBLIC_RAZORPAY_KEY_ID=rzp_live_xxxxxxxx
Anything prefixed with NEXT_PUBLIC_ ships to the browser, so never put RAZORPAY_KEY_SECRET in one. The NEXT_PUBLIC_RAZORPAY_KEY_ID is safe to expose — it is public by design.
Step 2: Create the Order API Route
App Router route handler, app/api/razorpay/create-order/route.ts:
ts[object Object], { ,[object Object],, ,[object Object], } ,[object Object], ,[object Object],; ,[object Object], ,[object Object], ,[object Object], ,[object Object],; ,[object Object], razorpay = ,[object Object], ,[object Object],({ ,[object Object],: process.,[object Object],.,[object Object],!, ,[object Object],: process.,[object Object],.,[object Object],!, }); ,[object Object], ,[object Object], ,[object Object], ,[object Object],(,[object Object],) { ,[object Object], { amount, currency = ,[object Object],, receipt } = ,[object Object], req.,[object Object],(); ,[object Object], order = ,[object Object], razorpay.,[object Object],.,[object Object],({ ,[object Object],: ,[object Object],.,[object Object],(amount * ,[object Object],), ,[object Object], currency, receipt, }); ,[object Object], ,[object Object],.,[object Object],(order); }
Razorpay bills in paise, so multiply rupees by 100 before sending. This single mistake causes more failed checkouts than any other.
Step 3: Build the Checkout Component
tsx[object Object],; ,[object Object], ,[object Object], ,[object Object], ,[object Object],; ,[object Object], ,[object Object], { ,[object Object], ,[object Object], { ,[object Object],: ,[object Object], } } ,[object Object], ,[object Object], ,[object Object], ,[object Object],(,[object Object],) { ,[object Object], ,[object Object], = ,[object Object], (,[object Object],) => { ,[object Object], res = ,[object Object], ,[object Object],(,[object Object],, { ,[object Object],: ,[object Object],, ,[object Object],: { ,[object Object],: ,[object Object], }, ,[object Object],: ,[object Object],.,[object Object],({ amount, receipt }), }); ,[object Object], order = ,[object Object], res.,[object Object],(); ,[object Object], rzp = ,[object Object], ,[object Object],.,[object Object],({ ,[object Object],: process.,[object Object],.,[object Object],, ,[object Object],: order.,[object Object],, ,[object Object],: order.,[object Object],, ,[object Object],: order.,[object Object],, ,[object Object],: ,[object Object], ,[object Object],(,[object Object],, { ,[object Object],: ,[object Object],, ,[object Object],: { ,[object Object],: ,[object Object], }, ,[object Object],: ,[object Object],.,[object Object],(resp), }), }); rzp.,[object Object],(); }; ,[object Object], ( ,[object Object], ); }
Step 4: Verify the Signature Server-Side
ts[object Object], crypto ,[object Object], ,[object Object],; ,[object Object], { ,[object Object],, ,[object Object], } ,[object Object], ,[object Object],; ,[object Object], ,[object Object], ,[object Object], ,[object Object],(,[object Object],) { ,[object Object], { razorpay_order_id, razorpay_payment_id, razorpay_signature } = ,[object Object], req.,[object Object],(); ,[object Object], expected = crypto .,[object Object],(,[object Object],, process.,[object Object],.,[object Object],!) .,[object Object],(,[object Object],) .,[object Object],(,[object Object],); ,[object Object], (expected !== razorpay_signature) { ,[object Object], ,[object Object],.,[object Object],({ ,[object Object],: ,[object Object], }, { ,[object Object],: ,[object Object], }); } ,[object Object], ,[object Object], ,[object Object],.,[object Object],({ ,[object Object],: ,[object Object], }); }
Handling the Captured Webhook
The verify route covers the redirect-time handshake, but the source of truth for fulfilment should be Razorpay's payment.captured webhook. Register the webhook URL in the Razorpay dashboard, and in the handler:
- Confirm the payload signature with the
x-razorpay-signatureheader (the same HMAC, computed over the raw body). - Update the order status only if the amount matches the stored order amount.
- Make the update idempotent — Razorpay retries delivery, and double-processing an order is a fast way to anger customers.
Production Rules That Prevent Chargebacks
- Verify the HMAC signature before touching order status — always.
- Create the order in your database before opening checkout, then reconcile with the
payment.capturedwebhook. - Enforce amount and currency server-side; never trust the client.
- Keep the amount in paise consistently across order, checkout and webhook.
For the surrounding stack, our guide on how to improve website conversion rate covers checkout drop-off, and the how to optimize website speed for SEO playbook keeps payment pages fast on slow networks. If you are upgrading an older codebase, our Next.js 16 upgrade guide shows what changed in the App Router.
Ship the flow above, test with Razorpay's test keys first, and you have a payment stack that survives a real launch day.