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
- Запуск:
npm run start:devилиnest start --watchна порту 3000. - Откройте:
npx portpreview 3000. - Зарегистрируйте URL туннеля + путь webhook.
- Отправьте тестовые доставки.
- Повторите дубли 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 бесплатно.
