Все статьи
NestJS-сервер с rawBody проверяет подписи вебхука из req.rawBody через guard, nest start открыт через локальный туннель.
NestJSNode.jswebhook debugginglocal testing

Вебхуки NestJS: rawBody, signature guards и ловушки ValidationPipe

Webhook-обработчики NestJS ломаются незаметно. Глобальный ValidationPipe удаляет поля до guard, а стандартный JSON-парсинг уничтожает подписанные байты. Локальное тестирование должно подтвердить rawBody: true, signature guards и точечную валидацию до вставки URL туннеля в Stripe или GitHub.

Включить raw body при bootstrap

Передайте rawBody: true при создании приложения Nest, чтобы 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() vs req.rawBody для подписей

Никогда не проверяйте подпись через @Body() — Nest уже распарсил объект. Читайте buffer из raw-body middleware:

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

Парсите JSON только после guard. Порядок: guard, затем десериализация.

Signature guard до эффектов ValidationPipe

Инкапсулируйте проверку провайдера в guard с чтением заголовков и constant-time сравнением:

@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 через @UseGuards только на webhook-маршруте.

Избегайте ловушки глобального ValidationPipe

Глобальный ValidationPipe с transform: true может менять типы до контроллера. 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 пустым. Guards должны работать на raw buffer.

Порядок глобального JSON middleware

Express json() до raw-body потребляет stream. Сохраняйте rawBody: true при создании factory.

Медленные handler и повторы

Быстро возвращайте 200 после проверки, ставьте тяжёлую работу в очередь, дедуплицируйте по event ID.

Дальше

Основы — в основах туннелирования localhost и практической локальной отладке вебхуков. Механика подписей — в руководстве по проверке подписи. Безопасные от повторов обработчики — в паттернах повторов и идемпотентности. начните PortPreview бесплатно.

Часто задаваемые вопросы

Почему req.rawBody undefined в webhook NestJS?
Передайте rawBody: true в NestFactory.create. Иначе Express не прикрепит raw buffer.
Можно ли проверять подпись через @Body()?
Нет. @Body() возвращает распарсенный объект, не подписанные байты. Используйте req.rawBody с rawBody: true.
Как тестировать NestJS вебхуки локально?
Запустите nest start на 3000, откройте через npx portpreview, зарегистрируйте URL туннеля и отправьте тестовые события.