Todos os artigos
Servidor NestJS com rawBody verifica assinaturas webhook de req.rawBody via guard, nest start exposto por túnel local.
NestJSNode.jswebhook debugginglocal testing

Webhooks no NestJS: rawBody, guards de assinatura e armadilhas ValidationPipe

Handlers webhook NestJS falham de forma subtil. Um ValidationPipe global remove campos antes do guard e o parsing JSON por defeito destrói os bytes assinados. Testes locais devem provar rawBody: true, guards de assinatura e validação por rota antes de colar URL de túnel no Stripe ou GitHub.

Ativar corpo bruto no bootstrap

Passe rawBody: true ao criar a app Nest para o Express manter um buffer intacto:

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

Sem esta flag, req.rawBody é undefined e cada HMAC falha.

@Body() vs req.rawBody para assinaturas

Nunca verifique assinaturas com @Body() — o Nest já parseou o objeto. Leia o buffer do 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 };
}

Parse JSON só após o guard confirmar. Ordem: guard primeiro, deserializar depois.

Guard de assinatura antes dos efeitos do ValidationPipe

Encapsule a verificação do provedor num guard que lê cabeçalhos e 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);
  }
}

Aplique o guard com @UseGuards apenas na rota webhook.

Evitar a armadilha do ValidationPipe global

Um ValidationPipe global com transform: true pode forçar tipos antes do controller. Rotas webhook precisam de rawBody + JSON.parse manual após verificação.

Teste local nest start + PortPreview

  1. Inicie: npm run start:dev ou nest start --watch na porta 3000.
  2. Exponha: npx portpreview 3000.
  3. Registe URL do túnel + path webhook.
  4. Dispare entregas de teste.
  5. Repita event IDs duplicados — veja padrões de retry.

Armadilhas comuns

ValidationPipe antes da assinatura

Se a validação correr primeiro, o Nest pode transformar o body e deixar rawBody vazio. Guards devem correr no buffer bruto.

Ordem do middleware JSON global

Express json() antes de raw-body consome o stream. Mantenha rawBody: true na criação da factory.

Handlers lentos e retries

Devolva 200 rápido após verificação, enfileire trabalho pesado e deduplique por event ID.

Para aprofundar

Para fundamentos, leia noções de tunneling localhost e depuração local de webhooks. Para mecânica de assinatura, veja o guia de verificação de assinatura. Para handlers sem duplicatas, leia padrões de retry e idempotência. comece PortPreview grátis.

Perguntas frequentes

Por que req.rawBody é undefined no webhook NestJS?
Passe rawBody: true ao NestFactory.create. Sem isso, o Express não anexa o buffer bruto.
Posso verificar assinaturas com @Body()?
Não. @Body() devolve um objeto parseado, não os bytes assinados. Use req.rawBody com rawBody: true.
Como testar webhooks NestJS localmente?
Execute nest start na porta 3000, exponha com npx portpreview, registe a URL do túnel e dispare eventos de teste.