Integrating Razorpay payments in Next.js: a step-by-step guide
Set up checkout, verify signatures on the server, and handle webhooks safely when integrating Razorpay with Next.js.
On this page
Payments are unforgiving: a single missing verification step is a security hole. The flow below keeps trust on the server and the browser as thin as possible.
Create the order on the server
Never let the client decide the amount.
export async function POST(request: Request) {
const { planId } = await request.json();
const plan = await getPlan(planId);
if (!plan) return new Response("Unknown plan", { status: 400 });
const order = await razorpay.orders.create({
amount: plan.amountInPaise,
currency: "INR",
receipt: `receipt_${Date.now()}`,
});
return Response.json({ orderId: order.id, amount: order.amount });
}
The client receives an order id, not a price it can edit.
Open checkout on the client
const { orderId, amount } = await fetch("/api/payments/order", {
method: "POST",
body: JSON.stringify({ planId }),
}).then((res) => res.json());
const checkout = new Razorpay({
key: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID,
order_id: orderId,
amount,
handler: async (response) => {
await fetch("/api/payments/verify", {
method: "POST",
body: JSON.stringify(response),
});
},
});
checkout.open();
Verify the signature server-side
This is the step that makes the payment trustworthy.
const expected = crypto
.createHmac("sha256", process.env.RAZORPAY_KEY_SECRET!)
.update(`${orderId}|${paymentId}`)
.digest("hex");
if (expected !== signature) {
return new Response("Invalid signature", { status: 400 });
}
Always handle webhooks
The browser can disappear between payment and callback. Webhooks are the source of truth.
- Verify the webhook signature
- Make the handler idempotent using the event id
- Fulfil the order from the webhook, not the client callback
Treat the client callback as a hint and the webhook as the fact.
Summary
Create orders on the server, verify every signature, and fulfil from webhooks. Everything else is presentation.
Continue reading
Related articles
Serverless file uploads with Supabase Storage and Next.js API routes
Secure, serverless file uploads in Next.js using Supabase Storage, with validation, size limits, and public access handling.
Building a modern todo app with Zustand, Firebase, and Next.js
A real-time todo app with authentication, CRUD operations, and predictable global state, built with Next.js, Firebase, and Zustand.
How I built a fullstack authentication system with Next.js, Supabase, and Tailwind
Email sign-in, session handling, and protected routes in a Next.js App Router app, built on Supabase auth with a clean Tailwind interface.