O Laravel protege cada rota web com CSRF. Provedores de webhook enviam POST sem cookie de sessão nem token CSRF — recebe 419 antes do controller. Solução: isenção CSRF direcionada e corpo bruto com $request->getContent().
Isentar rotas webhook do CSRF
Adicione a URI ao array $except em App\Http\Middleware\VerifyCsrfToken (Laravel 11+: middleware em bootstrap/app.php):
protected $except = [
'webhooks/stripe',
'webhooks/*',
];
Isole exceções a paths server-to-server — nunca desative CSRF globalmente.
Ler corpo bruto para verificação de assinatura
Após contornar CSRF, leia bytes intactos:
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);
}
Use getContent(), não all() ou input() — helpers Laravel não devolvem bytes assinados.
Verificar assinaturas antes da lógica de negócio
Extraia cabeçalhos, calcule digest do payload bruto, compare com hash_equals. Falha retorna 401. Veja guia de verificação de assinatura.
Configurar o túnel
- Inicie Laravel:
php artisan serve --port=8000. - Segundo terminal:
npx portpreview 8000. - Cole URL do túnel + path no dashboard.
- Com
TrustProxies, confie nos headers forwarded do túnel. - Dispare evento de teste.
Armadilhas comuns
Ler corpo duas vezes
getContent() após json() fica vazio. Leia uma vez, verifique, parse.
Ordem do middleware
Middleware global que parseia JSON antes do controller altera bytes. Isole rotas webhook.
Retries e idempotência
Persista IDs de evento — veja padrões de retry e idempotência.
Para aprofundar
Para fundamentos, leia noções de tunneling localhost e depuração local de webhooks. Para mecânica de assinatura, veja o guia de verificação de assinatura. Para handlers sem duplicatas, leia padrões de retry e idempotência. comece PortPreview grátis.
