GuardAPI Logo
GuardAPI

Fix Insecure Webhooks in Fastify

Webhooks are essentially unauthenticated entry points unless you implement cryptographic verification. If you're blindly trusting POST bodies in Fastify, you're asking for account takeovers or state manipulation. The core issue is that attackers can spoof the source headers and payload. To secure this, you must verify the HMAC signature provided by the provider (like Stripe or GitHub) using your shared secret and the raw request body.

The Vulnerable Pattern

fastify.post('/webhook', async (request, reply) => {
  // VULNERABILITY: No signature verification.
  // An attacker can send a fake 'payment_received' event to this endpoint.
  const { event, orderId } = request.body;

if (event === ‘order.paid’) { await db.orders.update(orderId, { status: ‘shipped’ }); }

return { received: true }; });

The Secure Implementation

The fix requires three critical steps. First, we override the content-type parser because HMAC verification must be performed on the exact raw string received over the wire; JSON.stringify() is non-deterministic and will break the hash. Second, we re-calculate the HMAC-SHA256 hash using the shared secret. Third, we use crypto.timingSafeEqual for the comparison. Using standard equality operators (==) allows an attacker to brute-force the signature byte-by-byte by measuring the time it takes for the server to return a 401 error.

const crypto = require('crypto');
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// 1. We need the raw body for HMAC verification before Fastify parses it fastify.addContentTypeParser(‘application/json’, { parseAs: ‘string’ }, (req, body, done) => { try { const json = JSON.parse(body); done(null, { …json, _rawBody: body }); } catch (err) { done(err); } });

fastify.post(‘/webhook’, async (request, reply) => { const signature = request.headers[‘x-signature’]; if (!signature) return reply.code(401).send(‘Missing signature’);

// 2. Compute HMAC using the raw string body const hmac = crypto.createHmac(‘sha256’, WEBHOOK_SECRET); const digest = hmac.update(request.body._rawBody).digest(‘hex’);

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

if (untrusted.length !== trusted.length || !crypto.timingSafeEqual(trusted, untrusted)) { return reply.code(401).send(‘Invalid signature’); }

const { event, orderId } = request.body; // Logic only executes if signature is valid return { status: ‘verified’ }; });

System Alert • ID: 6816
Target: Fastify API
Potential Vulnerability

Your Fastify API might be exposed to Insecure Webhooks

74% of Fastify 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.