安全接收并验证webhook需:1.用file_get_contents('php://input')一次性读原始体;2.依平台文档取对应签名头(如x-hub-signature-256);3.用hash_hmac+hash_equals比对hmac-sha256签名;4.验签后立即返回200,异步处理业务。

如何安全接收并验证 Webhook 请求的签名
第三方平台(如 GitHub、Stripe、微信支付)发来的 Webhook 请求必须验签,否则可能被伪造或重放。PHP 没有内置验签函数,得手动比对 HMAC-SHA256 或 md5 等签名字段。
常见错误是直接用 $_POST 读取数据——很多平台(如 GitHub)发的是 application/json 原始 body,此时 $_POST 为空,必须用 file_get_contents('php://input') 获取原始字节。
- 先确认平台文档要求的签名头名(如 GitHub 是
X-Hub-Signature-256,Stripe 是Stripe-Signature) - 用
hash_hmac('sha256', $raw_body, $secret, true)计算期望签名,再hash_equals()安全比对(防止时序攻击) - 注意:
$raw_body必须和平台发送的字节完全一致(不能提前json_decode或 trim) - 若平台使用 RSA 公钥验签(如微信支付),需用
openssl_verify(),且公钥要 PEM 格式、不含空格换行
为什么 file\_get\_contents('php://input') 有时读不到数据
根本原因是 PHP 的输入流只能读一次,且受 enable\_post\_data\_reading 和 Content-Type 影响。一旦触发了 $_POST 或 $_FILES 自动解析,php://input 就变为空。
- 检查是否在代码开头之前已有框架或中间件读取过请求体(如 Laravel 的
Illuminate\Http\Request构造函数会预读) - 确认
Content-Type不是multipart/form-data—— 这种类型下php://input在默认配置下不可用 - 临时调试可加
var_dump(headers_sent(), ini_get('enable_post_data_reading')); - 生产环境建议统一用
file_get_contents('php://input')+ 手动解析 JSON,避免依赖$_POST
如何避免 Webhook 处理超时导致平台重试
GitHub 默认 10 秒超时,Stripe 是 30 秒,超时即判定失败并重试。PHP 默认执行时间是 30 秒,但实际网络 I/O、数据库写入、日志落盘都可能拖慢响应。
- 收到请求后立即返回 HTTP 200(哪怕逻辑还没跑完),用
fastcgi_finish_request()(PHP-FPM)或ignore_user_abort(true)+ 后台异步处理 - 不要在 Webhook 入口处做耗时操作:如调用外部 API、生成 PDF、发送邮件——这些应进队列(Redis、Beanstalkd)或交由 CLI 脚本处理
- 记录原始请求体到数据库或文件(含
HTTP_X_FORWARDED_FOR、HTTP_USER_AGENT),方便排查重放或重复请求 - 加幂等性校验:提取平台提供的唯一 ID(如
X-GitHub-Delivery、idempotency-key),入库前查重
JSON 解析失败的典型原因和快速定位方法
json_last_error() 返回 JSON_ERROR_SYNTAX 很常见,但不等于数据本身非法——更可能是编码、BOM、不可见字符或截断导致。
- 先用
bindec(decbin(ord($raw_body[0]))) === 0xFEFF检查 UTF-8 BOM,有则用ltrim($raw_body, "\xEF\xBB\xBF")去掉 - 用
mb_detect_encoding($raw_body, ['UTF-8', 'GBK'], true)看是否混入非 UTF-8 字节(如微信某些旧接口会发 GBK 编码的中文) - 打印
strlen($raw_body)和mb_strlen($raw_body, 'UTF-8')对比,差值大说明存在非法多字节序列 - 别用
json_decode($raw_body, true, 512, JSON_INVALID_UTF8_IGNORE)掩盖问题——它会静默丢弃非法字符,可能导致业务字段丢失
event 对象里 data.object 可能是部分更新,不是全量快照;GitHub 的 pull_request 事件在不同 action 下字段结构完全不同。上线前务必用真实回调 payload 多测几次,别只信文档示例。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











