Architecting Secure Payment Gateways & Webhooks in Next.js
Why frontend payment confirmations are inherently dangerous, and how to build server-authoritative HMAC webhook handlers with idempotency.
The Core Vulnerability: Trusting Client-Side Callbacks
When integrating modern payment gateways such as Razorpay, Cashfree, or Stripe into web applications, the most critical architectural error is trusting the client browser to confirm payment success.
Most payment modal SDKs execute a client-side callback upon successful card entry or UPI authorization. Relying on this callback to update invoice statuses or provision goods creates severe security risks: an attacker can easily forge the HTTP request, or a network drop can leave customer funds deducted while your backend never receives the confirmation.
The foundational rule of payment engineering is server authority: only cryptographically signed server-to-server notifications or direct gateway status polls can transition a payment state to paid.
Server-Authoritative Order Creation
Before opening any checkout modal on the client, your server must initiate the order with the gateway provider using private API credentials. The generated gateway order ID is persisted in your database alongside expected amounts and user references.
// app/api/checkout/create-order/route.ts
import { NextResponse } from "next/server";
import { razorpayInstance } from "@/lib/payments/razorpay";
import prisma from "@/lib/db";
export async function POST(req: Request) {
const { invoiceId, amountInPaise } = await req.json();
// 1. Verify invoice state and ownership server-side
const invoice = await prisma.invoice.findUnique({ where: { id: invoiceId } });
if (!invoice || invoice.status === "PAID") {
return NextResponse.json({ error: "Invalid invoice" }, { status: 400 });
}
// 2. Generate authoritative order with gateway provider
const order = await razorpayInstance.orders.create({
amount: amountInPaise,
currency: "INR",
receipt: `rcpt_${invoice.invoiceNumber}`,
});
// 3. Store pending payment transaction record
await prisma.paymentTransaction.create({
data: {
invoiceId: invoice.id,
gatewayOrderId: order.id,
amount: invoice.totalAmount,
status: "PENDING",
},
});
return NextResponse.json({ orderId: order.id, keyId: process.env.NEXT_PUBLIC_RAZORPAY_KEY });
}HMAC SHA-256 Webhook Verification
Webhooks provide asynchronous confirmation directly from the gateway infrastructure. To guarantee authenticity, gateway providers sign the webhook payload using HMAC SHA-256 with a shared secret.
In Next.js App Router, ensure you verify the signature against the raw unparsed request buffer using timing-safe comparison to prevent timing attacks. Parsing the request body as JSON before verification can reorder object keys or alter whitespace, causing false signature verification failures.
import crypto from "crypto";
export function verifyWebhookSignature(
rawBody: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const expectedBuffer = Buffer.from(expectedSignature, "utf8");
const actualBuffer = Buffer.from(signature, "utf8");
if (expectedBuffer.length !== actualBuffer.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuffer, actualBuffer);
}Common Implementation Mistakes in Webhook Handlers
Even experienced engineering teams encounter recurring pitfalls when deploying payment webhooks to production:
Idempotency and Settlement Reconciliation
Gateway webhooks are designed for at-least-once delivery. If your endpoint takes longer than a few seconds to respond, the gateway will retry delivery, potentially sending identical events multiple times.
To learn how to handle duplicate delivery safely, explore our detailed companion guide on Payment Webhook Retry, Idempotency and Failure Handling. Review our Payment Terms for formal milestone agreements and dispute reconciliation protocols.
Production Security & Deployment Checklist
Before taking a payment integration live, verify each architectural safeguard:
Related Engineering Guides
Payment Webhook Retry, Idempotency and Failure Handling | Adhya Enterprises
How to engineer resilient payment webhook receivers that survive provider retries, concurrency races, and network timeouts with effectively-once business effects.
Building Scalable Multi-Tenant SaaS with PostgreSQL & Prisma
A deep dive into organization boundaries, foreign key integrity, and preventing tenant data leakage in enterprise SaaS platforms.