すべての記事
rawBody 有効な NestJS サーバーが guard 経由で req.rawBody から webhook 署名を検証し、nest start をローカルトンネルで公開。
NestJSNode.jswebhook debugginglocal testing

NestJS Webhook:rawBody、署名 guard、ValidationPipe の罠

NestJS の webhook handler は微妙に失敗します。グローバル ValidationPipe が guard 前にフィールドを削除し、デフォルト JSON パースが署名対象のバイトを壊します。Stripe や GitHub にトンネル URL を貼る前に rawBody: true、署名 guard、ルート限定検証をローカルで確認してください。

bootstrap で raw body を有効化

Nest アプリ作成時に rawBody: true を渡し、Express が未変更 buffer を保持:

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

このフラグがないと req.rawBody は undefined で HMAC がすべて失敗します。

署名には @Body() ではなく req.rawBody

@Body() で署名検証しない — Nest は既にオブジェクトを parse 済み。raw-body middleware の buffer を読む:

@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 };
}

guard 確認後にのみ JSON を parse。順序:guard → デシリアライズ。

ValidationPipe より前に signature guard

プロバイダー検証を guard にカプセル化し、ヘッダー読み取りと定数時間比較:

@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);
  }
}

guard は webhook ルートのみ @UseGuards で適用。

グローバル ValidationPipe の罠を避ける

transform: true のグローバル ValidationPipe はコントローラ前に型を強制。webhook は rawBody + 検証後の手動 JSON.parse が必要。

nest start + PortPreview でローカルテスト

  1. 起動: npm run start:dev または nest start --watch ポート 3000。
  2. 公開: npx portpreview 3000
  3. トンネル URL + webhook パスを登録。
  4. テスト配信をトリガー。
  5. 重複 event ID を再生 — リトライパターン

よくある落とし穴

署名前の ValidationPipe

検証が先だと Nest が body を変形し rawBody が空に。guard は raw buffer で実行。

グローバル JSON middleware の順序

raw-body 前の Express json() は stream を消費。rawBody: true を factory 作成時に維持。

遅い handler とリトライ

検証後すぐ 200、重い処理はキュー、event ID で重複排除。

さらに学ぶ

基礎はlocalhost トンネリングローカル webhook デバッグ。署名の仕組みは署名検証ガイド。重複安全なハンドラーはリトライと冪等性のパターンPortPreview を無料で始める

よくある質問

NestJS webhook で req.rawBody が undefined になるのはなぜ?
NestFactory.create に rawBody: true を渡してください。なければ Express は raw buffer を付けません。
NestJS で @Body() から署名検証できる?
いいえ。@Body() は parse 済みオブジェクトで、署名対象バイトと一致しません。rawBody: true で req.rawBody を使ってください。
NestJS webhook をローカルでテストするには?
3000 で nest start を実行し、npx portpreview で公開、トンネル URL を登録してテストイベントを送信します。