Todos los artículos
Un servidor NestJS con rawBody verifica firmas webhook desde req.rawBody vía guard, nest start expuesto por túnel local.
NestJSNode.jswebhook debugginglocal testing

Webhooks en NestJS: rawBody, guards de firma y trampas de ValidationPipe

Los handlers webhook de NestJS fallan de forma sutil. Un ValidationPipe global elimina campos antes del guard y el parsing JSON por defecto destruye los bytes firmados. Las pruebas locales deben demostrar rawBody: true, guards de firma y validación acotada antes de pegar una URL de túnel en Stripe o GitHub.

Habilitar cuerpo crudo en el bootstrap

Pasa rawBody: true al crear la app Nest para que Express conserve un buffer intacto:

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { rawBody: true });
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  await app.listen(3000);
}

Sin este flag, req.rawBody es undefined y cada HMAC falla aunque la red sea correcta.

@Body() vs req.rawBody para firmas

Nunca verifiques firmas contra @Body() — Nest ya parseó el objeto. Lee el buffer del middleware raw-body:

@Post('stripe')
@UseGuards(StripeSignatureGuard)
handleStripe(@Req() req: RawBodyRequest) {
  const payload = req.rawBody;
  const event = JSON.parse(payload.toString('utf8'));
  this.events.process(event);
  return { received: true };
}

Parsea JSON solo tras confirmar el guard. Orden: guard primero, deserializar después.

Guard de firma antes de efectos de ValidationPipe

Encapsula la verificación del proveedor en un guard que lee cabeceras y compara digests:

@Injectable()
export class StripeSignatureGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const req = context.switchToHttp().getRequest>();
    const sig = req.headers['stripe-signature'] as string;
    return verify(req.rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET);
  }
}

Aplica el guard con @UseGuards solo en la ruta webhook.

Evitar la trampa del ValidationPipe global

Un ValidationPipe global con transform: true puede coercionar tipos antes del controlador. Las rutas webhook necesitan rawBody + JSON.parse manual tras verificación.

Pruebas locales con nest start + PortPreview

  1. Inicia: npm run start:dev o nest start --watch en puerto 3000.
  2. Expón: npx portpreview 3000.
  3. Registra URL del túnel + ruta webhook.
  4. Dispara entregas de prueba.
  5. Repite event IDs duplicados — ver patrones de reintentos.

Trampas comunes

ValidationPipe antes de la firma

Si la validación corre primero, Nest puede transformar el body y dejar rawBody vacío. Los guards deben ejecutarse sobre el buffer crudo.

Orden del middleware JSON global

Express json() antes de raw-body consume el stream. Mantén rawBody: true al crear la factory.

Handlers lentos y reintentos

Devuelve 200 rápido tras verificar, encola trabajo pesado y deduplica por event ID.

Para profundizar

Para los fundamentos, lea los fundamentos del tunneling localhost y depuración práctica de webhooks en local. Para la criptografía, consulte la guía de verificación de firmas. Para handlers sin duplicados, lea patrones de reintentos e idempotencia. empiece PortPreview gratis.

Preguntas frecuentes

¿Por qué req.rawBody es undefined en mi webhook NestJS?
Pasa rawBody: true a NestFactory.create. Sin ello, Express no adjunta el buffer crudo.
¿Puedo verificar firmas con @Body()?
No. @Body() devuelve un objeto parseado distinto de los bytes firmados. Usa req.rawBody con rawBody: true.
¿Cómo probar webhooks NestJS en local?
Ejecuta nest start en 3000, expón con npx portpreview, registra la URL del túnel y dispara eventos de prueba.