Laravel включает CSRF на каждом web-маршруте. Провайдеры вебхуков шлют POST без cookie сессии и CSRF-токена — вы получаете 419 до контроллера. Решение: точечное исключение CSRF и сырой body через $request->getContent().
Исключить webhook-маршруты из CSRF
Добавьте URI в массив $except в App\Http\Middleware\VerifyCsrfToken (Laravel 11+: middleware в bootstrap/app.php):
protected $except = [
'webhooks/stripe',
'webhooks/*',
];
Исключайте только server-to-server пути — не отключайте CSRF глобально.
Читать сырой body для проверки подписи
После обхода CSRF читайте нетронутые байты:
public function handle(Request $request)
{
$payload = $request->getContent(); // raw string
$signature = $request->header('Stripe-Signature', '');
if (!$this->verifySignature($payload, $signature)) {
return response('', 401);
}
$event = json_decode($payload, true, 512, JSON_THROW_ON_ERROR);
$this->processEvent($event);
return response('', 200);
}
Используйте getContent(), не all() или input() — хелперы Laravel не дают подписанные байты.
Проверять подпись до бизнес-логики
Извлеките заголовки, вычислите digest из сырого payload и сравните через hash_equals. При ошибке — 401. См. руководство по проверке подписи.
Настроить туннель
- Запустите Laravel:
php artisan serve --port=8000. - Второй терминал:
npx portpreview 8000. - Вставьте URL туннеля + путь в dashboard провайдера.
- С
TrustProxiesдоверяйте forwarded-заголовкам туннеля. - Отправьте тестовое событие.
Типичные ловушки
Двойное чтение body
getContent() после json() пуст. Читайте один раз, проверяйте, парсите.
Порядок middleware
Глобальный JSON-парсер до контроллера меняет байты. Изолируйте webhook-маршруты.
Повторы и идемпотентность
Сохраняйте ID событий — см. паттерны повторов и идемпотентности.
Дальше
Основы — в основах туннелирования localhost и практической локальной отладке вебхуков. Механика подписей — в руководстве по проверке подписи. Безопасные от повторов обработчики — в паттернах повторов и идемпотентности. начните PortPreview бесплатно.
