Razorpay Integration Patterns: Orders, Signatures and Idempotent Webhooks

Server-priced orders, timing-safe signature checks, webhooks that survive retries, and reconciliation — the payment patterns we use on every Indian commerce build.

DSDilawar Singh 2 min read 3,641 views
Razorpay Integration Patterns: Orders, Signatures and Idempotent Webhooks

Razorpay's Checkout makes the happy path short: create an order, open the widget, verify the response. Most payment incidents we are asked to fix come from what happens around that path — a price taken from the browser, a webhook processed twice, or a payment that succeeded at the gateway but never reached the order. These are the patterns we use to make those failures impossible or recoverable.

Price the order on the server

The browser sends item IDs and quantities, never amounts. The server loads prices, applies coupons and taxes, and creates the Razorpay order in the smallest currency unit (cents for USD).

const amountMinor = await pricing.totalFor(cart, viewer); // integer cents, server-side
const rzpOrder = await razorpay.orders.create({
  amount: amountMinor,
  currency: 'USD',
  receipt: order.number,
  notes: { orderId: String(order._id) },
});
await Order.updateOne({ _id: order._id }, { $set: { gatewayOrderId: rzpOrder.id, amountMinor } });

Verify the checkout signature with a constant-time compare

After payment, Checkout returns razorpay_order_id, razorpay_payment_id and razorpay_signature. The signature is an HMAC-SHA256 of order_id|payment_id with your key secret. Use the order ID you stored, not the one the client sends back.

import crypto from 'node:crypto';

export function verifyPayment(storedOrderId: string, paymentId: string, signature: string, keySecret: string) {
  const expected = crypto.createHmac('sha256', keySecret).update(`${storedOrderId}|${paymentId}`).digest('hex');
  const a = Buffer.from(expected, 'utf8');
  const b = Buffer.from(signature, 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Make webhooks the source of truth

Users close tabs and networks drop, so the client-side callback is a fast path, not the only path. Subscribe to payment.captured and order.paid, verify the X-Razorpay-Signature header over the raw request body with your webhook secret, and make processing idempotent.

export async function POST(req: Request) {
  const raw = await req.text(); // raw body — do not parse before verifying
  if (!verifyWebhook(raw, req.headers.get('x-razorpay-signature') ?? '', WEBHOOK_SECRET)) return new Response('invalid', { status: 400 });
  const eventId = req.headers.get('x-razorpay-event-id');
  const fresh = await WebhookEvent.create({ _id: eventId }).then(() => true, () => false); // unique id = dedupe
  if (fresh) await fulfilment.fromWebhook(JSON.parse(raw)); // same idempotent function as the client path
  return new Response('ok');
}

Both the client confirmation and the webhook call the same fulfilment function, guarded by an atomic status transition (pending → paid), so whichever arrives second does nothing.

Reconcile daily

Run a scheduled job that lists captured payments for the previous day from the gateway and compares them with paid orders. Anything captured but not fulfilled is fixed automatically or raised to finance. In our experience this job pays for itself the first time a webhook endpoint is misconfigured during a deploy.

Refunds and records

  • Issue refunds from the server with an idempotency key tied to your refund record.
  • Store every gateway identifier (order, payment, refund) on your own records for support and audits.
  • Generate GST-compliant invoices from your data, not from gateway payloads.

Key takeaways

  • The server decides the amount; the browser never does.
  • Verify signatures with stored IDs and constant-time comparison.
  • Treat webhooks as the source of truth and dedupe by event ID.
  • Share one idempotent fulfilment path between client and webhook.
  • Reconcile with the gateway every day.
DS

Written by

Dilawar Singh

Chief Technology Officer, DEV3 Technologies

DEV3 Technologies has delivered web platforms, AI systems, SaaS products and mobile apps since 2025. Articles are written and reviewed by the engineers who build and operate these systems.

Keep reading

All articles