Les handlers webhook NestJS échouent de façon subtile. Un ValidationPipe global supprime des champs avant le guard, et le parsing JSON par défaut détruit les octets signés par le provider. Le test local doit prouver rawBody: true, des guards de signature et une validation ciblée avant de coller une URL tunnel dans Stripe ou GitHub.
Activer le corps brut au bootstrap
Passez rawBody: true à la création de l'application Nest pour qu'Express conserve un buffer intact :
async function bootstrap() {
const app = await NestFactory.create(AppModule, { rawBody: true });
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.listen(3000);
}
Sans ce flag, req.rawBody est undefined et chaque HMAC échoue même si le réseau est correct.
@Body() vs req.rawBody pour les signatures
Ne vérifiez jamais les signatures avec @Body() — Nest a déjà parsé l'objet. Lisez le buffer du 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 };
}
Parsez le JSON seulement après validation du guard. Ordre : guard d'abord, désérialisation ensuite.
Guard de signature avant les effets de ValidationPipe
Encapsulez la vérification provider dans un guard qui lit les en-têtes et compare les 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);
}
}
Appliquez le guard avec @UseGuards uniquement sur la route webhook.
Éviter le piège ValidationPipe global
Un ValidationPipe global avec transform: true peut coercer les types avant le contrôleur. Les routes webhook doivent utiliser rawBody + JSON.parse manuel après vérification.
Test local avec nest start + PortPreview
- Démarrez :
npm run start:devounest start --watchsur le port 3000. - Exposez :
npx portpreview 3000. - Enregistrez l'URL tunnel + chemin webhook.
- Déclenchez des livraisons test.
- Rejouez les event ID en double — voir patterns retry.
Pièges courants
ValidationPipe avant la signature
Si la validation passe en premier, Nest peut transformer le body et laisser rawBody vide. Les guards doivent s'exécuter sur le buffer brut.
Ordre du middleware JSON global
Le middleware json() Express avant raw-body consomme le stream. Gardez rawBody: true à la création et évitez les parsers dupliqués.
Handlers lents et retries
Renvoyez 200 vite après vérification, mettez le travail lourd en queue et dédupliquez par event ID.
Pour aller plus loin
Pour les bases, lisez les bases du tunneling localhost et le débogage webhook en local. Pour la cryptographie, consultez le guide de vérification de signature. Pour les handlers sans doublons, voyez les patterns retry et idempotence. commencez PortPreview gratuitement.
