Webhook路由须用POST且禁用CSRF防护,应注册于api.php或在VerifyCsrfToken的$except中排除;需用getContent()读原始body并手动解析;微信回调用text/xml需simplexml_load_string;签名验证须校验时间戳防重放、用hash_equals防时序攻击;密钥从配置读取;验证后立即返回200,耗时逻辑交由队列处理。

Webhook路由必须用 POST 且禁用 CSRF 防护
第三方服务(如 Stripe、微信支付、GitHub)发来的回调请求不会带 session 或 CSRF token,Laravel 默认中间件会直接返回 419 Page Expired。这不是配置错,是设计使然。
实操建议:
- 在
routes/web.php中避免注册 Webhook 路由;改用routes/api.php(默认不启用VerifyCsrfToken) - 若必须走
web中间件组,需在app/Http/Middleware/VerifyCsrfToken.php的$except数组里明确添加路径,例如:'webhook/stripe/*' - 路径尽量带版本前缀(如
/api/v1/webhook/wechat),方便后续灰度或迁移
Illuminate\Http\Request 无法直接读 json 原始体?
很多 API(比如 Slack、Shopify)回调时把数据放在原始 body 中,Content-Type 是 application/json,但 Laravel 的 $request->all() 可能为空——因为框架尝试从 php://input 解析失败,或被中间件提前处理过。
实操建议:
- 用
$request->getContent()拿原始字符串,再手动json_decode($request->getContent(), true) - 别依赖
$request->json()->all(),它在某些 Content-Type 不严格匹配时失效(比如application/json; charset=utf-8) - 加一层空值判断:
if (empty($request->getContent())) { abort(400, 'Empty body'); } - 微信回调更特殊:用的是
text/xml,得用simplexml_load_string(),不是 JSON
签名验证不只校验字段,更要防重放和时序攻击
光比对 X-Hub-Signature-256 或 wechat-signature 不够。攻击者截获一次合法请求,稍作延时重发,你的系统照样认为有效。
实操建议:
- 强制要求第三方在 header 中传
X-Timestamp(秒级时间戳),服务端检查是否落在 ±5 分钟窗口内 - 签名验证逻辑必须用
hash_equals()(PHP 内置防时序攻击函数),不要用===直接比较字符串 - Stripe 等平台还要求验证
idempotency-key或事件id是否已处理过,需查库去重(建议用INSERT ... ON CONFLICT DO NOTHING或 RedisSETNX) - 密钥别硬编码:从
config/services.php读取,生产环境走env()+.env,别塞进控制器里
异步处理回调逻辑,别卡住 HTTP 响应
Webhook 是“通知”不是“请求-响应”,你有几秒时间返回 200 OK,但不能在里面做耗时操作(如调用另一个 API、生成 PDF、发邮件)。超时会导致第三方重试,引发重复消费。
实操建议:
- 收到请求后,立刻验证签名+时间戳+基础字段,成功就
return response('', 200); - 把后续逻辑封装成 Job(如
ProcessStripeWebhook),用dispatch()推进队列 - Job 内部再查数据库、调外部服务、更新状态——失败自动进
failed_jobs表,可人工干预 - 别用
sync驱动测试时图省事,上线前务必切到redis或database驱动
最常被忽略的是:微信服务器要求 5 秒内响应,而本地开发时 Xdebug + 全局日志可能拖到 6 秒以上——压测时一定要关掉调试器和 verbose 日志。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











