typescriptadvanced

Webhook Handler with Signature Verification

Handle incoming webhooks with signature verification and idempotent processing.

typescript
// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createHmac, timingSafeEqual } from 'crypto';

function verifySignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const parts = Object.fromEntries(
    signature.split(',').map((part) => {
      const [key, value] = part.split('=');
      return [key, value];
    })
  );

  const timestamp = parts['t'];
  const sig = parts['v1'];

  // Prevent replay attacks
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
  if (age > 300) return false; // 5 min tolerance

  const signedPayload = `${timestamp}.${payload}`;
  const expected = createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

export async function POST(request: NextRequest) {
  const body = await request.text();
  const signature = request.headers.get('stripe-signature');

  if (!signature) {
    return NextResponse.json({ error: 'Missing signature' }, { status: 400 });
  }

  const secret = process.env.STRIPE_WEBHOOK_SECRET!;
  if (!verifySignature(body, signature, secret)) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
  }

  const event = JSON.parse(body);

  // Idempotent processing
  const processed = await isEventProcessed(event.id);
  if (processed) {
    return NextResponse.json({ received: true, duplicate: true });
  }

  switch (event.type) {
    case 'checkout.session.completed':
      await handleCheckoutComplete(event.data.object);
      break;
    case 'invoice.paid':
      await handleInvoicePaid(event.data.object);
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  await markEventProcessed(event.id);
  return NextResponse.json({ received: true });
}

Use Cases

  • payment webhooks
  • CMS events
  • third-party integrations

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.