Payment Solutions
August 15, 2026
7 min read
By Adhya Engineering Team

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.

typescriptproduction pattern
// 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.

typescriptproduction pattern
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:

Parsing request body before computing HMAC digest, leading to signature mismatch errors.
Failing to use timingSafeEqual, which exposes the system to cryptographic timing side-channel attacks.
Exposing webhook shared secrets or private API keys inside frontend JavaScript client bundles.
Assuming network delivery is strictly sequential; webhooks can arrive out of order during gateway retries.
Performing heavy external API calls synchronously inside the webhook handler, causing gateway request timeouts.

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.

Log raw incoming webhook payloads into an immutable PaymentEvent table for auditing.
Wrap balance updates and invoice status changes in an atomic database transaction.
Implement scheduled nightly reconciliation scripts matching gateway settlement reports against database records.

Production Security & Deployment Checklist

Before taking a payment integration live, verify each architectural safeguard:

All API secrets and webhook keys reside strictly in server-side environment variables.
Incoming webhook requests verify HMAC signatures against raw request buffers.
Payment processing routes enforce server-authoritative amount validation.
Database updates execute inside atomic transactions to prevent partial state updates.
Comprehensive payment event logging is active for post-settlement audit trails.
Related Service & Capability

Payment Solutions & Merchant Onboarding

Learn more about our production payment integration services, merchant onboarding assistance, and automated reconciliation ledgers.

Topics:#Payments#Next.js#Security#Webhooks#Idempotency

Have questions about implementing these patterns?

Our engineering team designs and operates custom systems, payment gateways, and SaaS architectures tailored to your specifications.