Real World Example

Payment Webhook Like Razorpay

After a customer pays, Razorpay (or Stripe, PhonePe) calls your server with a webhook. You must verify the signature, because anyone could POST fake "payment successful" events.

// app/api/webhooks/razorpay/route.ts
import crypto from 'node:crypto';
import { markOrderPaid } from '@/lib/orders';

export async function POST(req: Request) {
  const body = await req.text(); // raw body is needed for the signature
  const signature = req.headers.get('x-razorpay-signature') ?? '';
  const expected = crypto.createHmac('sha256', process.env.RAZORPAY_WEBHOOK_SECRET!).update(body).digest('hex');

  if (signature.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return Response.json({ error: 'Invalid signature' }, { status: 400 });
  }

  const event = JSON.parse(body) as { event: string; payload: { payment: { entity: { notes: { orderId: string } } } } };
  if (event.event === 'payment.captured') {
    await markOrderPaid(event.payload.payment.entity.notes.orderId);
  }
  return Response.json({ received: true });
}

Signature Check, Runnable

Example

The same HMAC check with node:crypto:

import crypto from 'node:crypto';

const secret = 'whsec_test_123';
const body = JSON.stringify({ event: 'payment.captured', orderId: 'ORD-1001', amount: 179900 });

function sign(payload: string): string {
  return crypto.createHmac('sha256', secret).update(payload).digest('hex');
}

function verify(payload: string, signature: string): boolean {
  const expected = sign(payload);
  return signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

const sig = sign(body);
console.log('signature :', sig.slice(0, 16) + '...');
console.log('genuine   :', verify(body, sig));
console.log('tampered  :', verify(body.replace('179900', '100'), sig));
console.log('bad header:', verify(body, 'abc'));

Output

signature : d4237d8af23937b5...
genuine   : true
tampered  : false
bad header: false