Laravel 为每条 web 路由启用 CSRF 保护。Webhook 提供商发送 POST 时不带 session cookie 或 CSRF token,控制器执行前就会返回 419。解决方案:针对 webhook 豁免 CSRF,并用 $request->getContent() 读取原始请求体。
为 webhook 路由豁免 CSRF
将 webhook URI 加入 App\Http\Middleware\VerifyCsrfToken 的 $except 数组(Laravel 11+ 在 bootstrap/app.php 注册 middleware):
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 辅助方法不会返回提供商签名的精确字节。
在业务逻辑前验证签名
提取提供商请求头,从原始 payload 计算 digest,用 hash_equals 比较。失败返回 401。参见 签名验证指南。
配置隧道
- 启动 Laravel:
php artisan serve --port=8000。 - 另一终端:
npx portpreview 8000。 - 将隧道 URL + 路径粘贴到提供商控制台。
- 配置
TrustProxies信任隧道的 forwarded 头。 - 触发测试事件。
常见陷阱
重复读取 body
json() 后调用 getContent() 为空。只读一次,验证后再解析。
中间件顺序
控制器前的全局 JSON 解析会改变字节。隔离 webhook 路由。
重试与幂等
持久化事件 ID — 见 重试与幂等性模式。
延伸阅读
基础阅读localhost 隧道基础和本地 webhook 调试。签名机制见签名验证指南。防重复处理见重试与幂等性模式。免费开始使用 PortPreview。
