In Next.js, payment code belongs in server-only modules and Route Handlers. A client component may request checkout from your own /api route and display the returned QR or payment URL, but it must never receive X-API-Key or the webhook secret. The server reloads the cart, calculates the amount, stores a stable payment reference, and calls VyaparGateway.
The webhook Route Handler reads request.arrayBuffer() before JSON parsing, verifies the timestamped HMAC, and applies an idempotent database transition. The customer return page reads your local order state; it does not trust query-string success parameters or a browser redirect as proof of payment.
Design principles
The boundaries that keep this flow reliable
Use server-only boundaries
Keep gateway configuration in a module imported only by Route Handlers, Server Actions, or backend workers. Never prefix secrets with NEXT_PUBLIC_.
Reprice inside the route
Accept cart identity from the client, then load products and calculate the total from trusted database values.
Keep webhook runtime compatible
Use a runtime with the cryptography and database features your verifier needs, and test exact raw-body behavior in deployment.
Implementation workflow
From request to a durable result
- 1
Create POST /api/checkout
Authenticate the customer, load the cart, persist client_txn_id, and call create_order with a server-only API key.
- 2
Render a pending order page
Return customer-safe fields and navigate to an order route that displays paid, pending, expired, or review state from your database.
- 3
Create POST /api/webhooks/vyapargateway
Read raw bytes, verify timestamp and HMAC, parse JSON, deduplicate event_id, and update the mapped order transactionally.
- 4
Add recovery
When a webhook is delayed, let a server job call check_order_status for unresolved orders with bounded backoff.
Next.js App Router webhook skeleton
ts// app/api/webhooks/vyapargateway/route.ts
import crypto from "node:crypto";
export const runtime = "nodejs";
export async function POST(request: Request) {
const raw = Buffer.from(await request.arrayBuffer());
const timestamp = request.headers.get("X-VyaparGateway-Timestamp") ?? "";
const signature = request.headers.get("X-VyaparGateway-Signature") ?? "";
const expected = crypto
.createHmac("sha256", process.env.VG_WEBHOOK_SECRET!)
.update(timestamp + ".")
.update(raw)
.digest("hex");
if (!safeEqual(expected, signature)) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(raw.toString("utf8"));
await acceptPaymentEventOnce(event); // transaction + unique event_id
return Response.json({ received: true });
} Production checklist
Verify before going live
- No payment secret uses the NEXT_PUBLIC_ prefix
- Checkout route authenticates user and reprices cart
- Webhook reads arrayBuffer before JSON.parse
- event_id and client_txn_id have database constraints
- Return page reads local server state
- Deployment logs redact secrets and customer payment data
Failure recovery
Next.js integration mistakes
API key in a client component
Anything shipped to the browser can be extracted. Move gateway calls to a Route Handler or protected backend service immediately.
Webhook works in dev only
Deployment runtime, proxy, or middleware may alter headers/body. Test the public HTTPS route with the Webhook Workbench.
Success page unlocks the order
A customer can open or modify that URL. Render state from your database after webhook or authenticated status verification.
FAQ
Questions developers ask
Can I call VyaparGateway from a Next.js Server Action?
A server-only action can call backend services, but a Route Handler is often clearer for checkout APIs and required for the public webhook endpoint.
Should the webhook use Edge runtime?
Choose the runtime supported by your cryptography, raw-body, database, and deployment stack. The example declares Node.js for the built-in crypto API.
How does the client know payment succeeded?
Render or refresh a server-owned order status that your webhook or reconciliation worker updates. Do not trust redirect query parameters.