Webhook事件接收失败主因是服务端未正确读取原始JSON请求体;PHP中$_POST无法解析JSON,须用file_get_contents('php://input')或Symfony Request::toArray()安全获取,并注意SubscriptionValidation事件需返回validationResponse。

Webhook 事件接收失败,90% 不是第三方没发,而是你的服务没正确读取或验证原始请求体。
为什么 $_POST 读不到 Webhook 数据?
Webhook 发送方(如 GitHub、DMS、Synology Surveillance Station)几乎都用 Content-Type: application/json 发送纯 JSON,而 $_POST 只处理表单编码数据。PHP 不会自动解析 JSON 到 $_POST,必须手动读取原始输入流。
- 错误写法:
$_POST['text1']→ 总是空,因为 JSON 没进$_POST - 正确做法:用
file_get_contents('php://input')获取原始字节 - 注意:
php://input在enctype="multipart/form-data"场景下不可用,但 Webhook 几乎不用这种编码
Composer 集成 symfony/http-foundation 后怎么安全读取?
直接操作 php://input 容易漏掉边界检查。用 symfony/http-foundation 可以统一抽象请求,避免重复造轮子。
- 安装:
composer require symfony/http-foundation - 关键点:它默认不解析 JSON 请求体,需显式调用
$request->toArray()(内部已处理php://input+json_decode+ 错误检查) - 示例代码片段:
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
if ($request->getContentType() !== 'json') {
http_response_code(400);
exit('Content-Type must be application/json');
}
try {
$data = $request->toArray(); // 自动校验 JSON 有效性
} catch (RuntimeException $e) {
http_response_code(400);
exit('Invalid JSON: ' . $e->getMessage());
}
- 优势:自动跳过
$_GET/$_POST干扰,不依赖always_populate_raw_post_data(已废弃)
如何兼容 Synology Surveillance Station 的 token 和 status 参数?
Synology 的 Webhook URL 是 GET 形式拼接参数(如 ?token=abc&status=active),但它的实际请求仍是 POST + JSON body。这意味着你既要解析 query string 中的认证参数,也要解析 request body 中的事件数据。
- 别只看 URL:
$_GET['token']可取,但$_GET['text1']不可靠(可能被 URL 编码破坏) - 正确顺序:先从
$_GET['token']校验合法性,再从php://input或$request->toArray()读业务字段(text1,text2,time) - 注意
status字段:Synology 会发两次(active/inactive),需在业务逻辑中区分状态生命周期 - token 验证建议:用
hash_equals()防时序攻击,不要用==
为什么返回 200 还收不到后续事件?
很多平台(如 Azure Event Grid、Convoy)在首次订阅时会发一个 SubscriptionValidationEvent,要求你同步返回 validationCode。如果只返回 200,没回传该字段,订阅就卡在 AwaitingManualAction 状态。
- 检查请求体是否含
data.validationCode(不是 query string) - 典型判断逻辑:
if (isset($data['eventType']) && $data['eventType'] === 'Microsoft.EventGrid.SubscriptionValidation') { echo json_encode(['validationResponse' => $data['data']['validationCode']]); exit; } - 这个响应必须是
200 OK+ JSON body,不能带额外 HTML 或空格 - 错过这次握手,后续所有事件都会被拦截,且无明确报错提示
最常被忽略的是:把验证事件当成普通业务事件处理,结果既没返回 validationResponse,又没记录日志,导致整个通道静默失效。


















