GuardAPI Logo
GuardAPI

Fix Insecure Webhooks in Next.js

Webhooks are public-facing endpoints. If you aren't verifying the cryptographic signature of the incoming payload, you're handing an unauthenticated RCE or logic-bypass vector to any script kiddie with your URL. In Next.js, 'trusting the body' is a fast track to IDOR and fraudulent transactions. Secure your endpoints using HMAC-SHA256 signature verification to ensure the request actually originated from your provider (Stripe, GitHub, etc.) and hasn't been tampered with in transit.

The Vulnerable Pattern

export async function POST(req) {
  // VULNERABILITY: Endpoint blindly trusts the JSON body.
  // An attacker can spoof this request to trigger business logic.
  const body = await req.json();

const { orderId, status } = body; if (status === ‘completed’) { await db.orders.update(orderId, { paid: true }); }

return new Response(‘Success’, { status: 200 }); }

The Secure Implementation

The secure implementation introduces three critical layers: 1. Payload Integrity: We use `req.text()` instead of `req.json()` because any difference in whitespace between the raw body and a parsed object will invalidate the HMAC. 2. Origin Authentication: The HMAC-SHA256 hash proves the sender possesses the shared secret. 3. Anti-Timing Protection: `crypto.timingSafeEqual` prevents attackers from brute-forcing the signature byte-by-byte by measuring the server's response time. Without these, your webhook is a wide-open door for unauthorized state changes.

import crypto from 'crypto';

export async function POST(req) { const signature = req.headers.get(‘x-webhook-signature’); const secret = process.env.WEBHOOK_SECRET;

if (!signature) { return new Response(‘Missing signature’, { status: 401 }); }

// Use raw text for signature verification to avoid JSON formatting discrepancies const rawBody = await req.text(); const hmac = crypto.createHmac(‘sha256’, secret); const digest = hmac.update(rawBody).digest(‘hex’);

// Use timingSafeEqual to prevent side-channel timing attacks const trusted = Buffer.from(digest, ‘ascii’); const untrusted = Buffer.from(signature, ‘ascii’);

if (untrusted.length !== trusted.length || !crypto.timingSafeEqual(trusted, untrusted)) { return new Response(‘Invalid signature’, { status: 401 }); }

const body = JSON.parse(rawBody); await db.orders.update(body.orderId, { paid: true });

return new Response(‘Verified’, { status: 200 }); }

System Alert • ID: 5030
Target: Next.js API
Potential Vulnerability

Your Next.js API might be exposed to Insecure Webhooks

74% of Next.js apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.

RUN FREE SECURITY DIAGNOSTIC
GuardLabs Engine: ONLINE

Free Tier • No Credit Card • Instant Report

Verified by Ghost Labs Security Team

This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.