Fix Insecure Webhooks in NestJS
Webhooks are unauthenticated entry points by design. If you aren't verifying the cryptographic signature of the incoming payload, you're effectively exposing a public API that allows any script kiddie to spoof events and manipulate your internal state. Stop trusting the 'origin' header and start verifying the HMAC.
The Vulnerable Pattern
@Controller('webhooks')
export class WebhookController {
@Post('stripe')
handleWebhook(@Body() payload: any) {
// VULNERABILITY: No signature verification.
// Anyone can POST a fake 'payment.succeeded' event to this endpoint.
return this.paymentService.fulfillOrder(payload.data.object.customer_email);
}
}
The Secure Implementation
The fix involves three critical steps. First, enable 'rawBody: true' in your main.ts NestJS factory to access the unmutated payload; verifying a signature against a pre-parsed JSON object will fail due to whitespace differences. Second, compute the HMAC-SHA256 hash using your shared secret and the raw body. Finally, use 'crypto.timingSafeEqual' for the comparison. Standard string comparison (==) is vulnerable to timing attacks where an attacker can determine the correct signature byte-by-byte based on response latency.
import { Controller, Post, Headers, Req, BadRequestException, RawBodyRequest } from '@nestjs/common'; import { createHmac, timingSafeEqual } from 'crypto';@Controller(‘webhooks’) export class WebhookController { @Post(‘secure-handler’) async handle(@Headers(‘x-signature’) signature: string, @Req() req: RawBodyRequest
) { const secret = process.env.WEBHOOK_SECRET; if (!signature) throw new BadRequestException(‘Missing signature’); // 1. Must use the raw body, not the parsed JSON object, for HMAC integrity const hmac = createHmac('sha256', secret); const digest = Buffer.from(hmac.update(req.rawBody).digest('hex'), 'utf8'); const checksum = Buffer.from(signature, 'utf8'); // 2. Use timingSafeEqual to prevent side-channel timing attacks if (checksum.length !== digest.length || !timingSafeEqual(digest, checksum)) { throw new BadRequestException('Invalid signature'); } return this.processEvent(JSON.parse(req.rawBody.toString()));
} }
Your NestJS API
might be exposed to Insecure Webhooks
74% of NestJS apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.
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.