You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Twilio Notify deliveryCallbackUrl嵌套请求签名验证正确格式

问题描述

我在开发基于Twilio Notify的批量短信通知功能时,为追踪单条消息发送状态,给notification资源配置了deliveryCallbackUrl,创建通知的代码如下:

$notification = $service->notifications->create([
 "toBinding" => $binding,
 "body" => $message_text,
 "deliveryCallbackUrl" => "https://username:password@mysite/notification_delivery_callback.php"
]);

我可以正常接收到回调payload,但做请求合法性校验时始终验证失败,验证逻辑代码如下:

use Twilio\Rest\Client;
use Twilio\Security\RequestValidator;

$configs = get_configs();

$signature = $_SERVER["HTTP_X_TWILIO_SIGNATURE"];
$request_url = $_SERVER['SCRIPT_URI'];

$validator = new RequestValidator($configs['twilio_auth_token']);
$request = $_REQUEST;

// $is_valid 始终返回false
$is_valid = $validator->validate($signature, $request_url, $request);

接收到的payload为嵌套结构,包含JSON格式内容,结构示例如下:

Array(    
  [NotificationSid] => NTxxxxxxxxx   
  [ServiceSid] => ISxxxxxxxxx   
  [Count] => 4    
  [IsFinal] => true   
  [DeliveryState] => Array(         
    [3] => {"status":"SENT","type":"sms","identity":"xxxxxxxxx","sid":"SMxxxxxxxxx"}            
    [2] => {"status":"SENT","type":"sms","identity":"xxxxxxxxx","sid":"SMxxxxxxxxx"}            
    [1] => {"status":"SENT","type":"sms","identity":"xxxxxxxxx","sid":"SMxxxxxxxxx"}           
    [0] => {"status":"SENT","type":"sms","identity":"xxxxxxxxx","sid":"SMxxxxxxxxx"}       
  )    
  [AccountSid] => ACxxxxxxxxx    
  [SequenceId] => 0
)

经排查DeliveryState嵌套数组是导致验证失败的原因:其他非嵌套结构的webhook请求都可以正常完成验证。查阅Twilio官方文档暂未找到嵌套请求的相关验证规则,先后尝试数组扁平化、JSON字符串转义、两种方案组合使用等处理方式,均无法通过验证,尝试过的参数格式如下:

Array(
  ...
  [DeliveryState] => Array(
    [0] => "{\"status\":\"SENT\",\"type\":\"sms\",\"identity\":\"xxxxxxxxx\",\"sid\":\"SMxxxxxxxxx\"}"   
    [1] => "{\"status\":\"SENT\",\"type\":\"sms\",\"identity\":\"xxxxxxxxx\",\"sid\":\"SMxxxxxxxxx\"}"
    [2] => "{\"status\":\"SENT\",\"type\":\"sms\",\"identity\":\"xxxxxxxxx\",\"sid\":\"SMxxxxxxxxx\"}"
    [3] => "{\"status\":\"SENT\",\"type\":\"sms\",\"identity\":\"xxxxxxxxx\",\"sid\":\"SMxxxxxxxxx\"}"
  )
  ...
)
Array(
  ...
  [0] => "{\"status\":\"SENT\",\"type\":\"sms\",\"identity\":\"xxxxxxxxx\",\"sid\":\"SMxxxxxxxxx\"}"   
  [1] => "{\"status\":\"SENT\",\"type\":\"sms\",\"identity\":\"xxxxxxxxx\",\"sid\":\"SMxxxxxxxxx\"}"
  [2] => "{\"status\":\"SENT\",\"type\":\"sms\",\"identity\":\"xxxxxxxxx\",\"sid\":\"SMxxxxxxxxx\"}"
  [3] => "{\"status\":\"SENT\",\"type\":\"sms\",\"identity\":\"xxxxxxxxx\",\"sid\":\"SMxxxxxxxxx\"}"
  ...
)

需要确认该类嵌套结构请求完成签名验证所需的正确参数格式。

原因与解决方案

验证失败由两个共同问题导致:

  • 基准URL不匹配:回调URL携带了基础认证凭据(https://username:password@mysite/...),Twilio计算签名时会自动剔除URL中的userinfo段(即username:password@部分),直接取$_SERVER['SCRIPT_URI']会把认证信息带入签名计算,直接导致签名不匹配。
  • 参数结构处理错误:Twilio的webhook签名逻辑只识别表单提交时的原始一维键值对,不会处理多维嵌套结构。PHP自动解析的$_REQUEST会把DeliveryState[0]这类表单键转换成嵌套数组,和Twilio签名时使用的参数结构不一致;额外做的数组扁平化、JSON转义操作属于多余处理,反而破坏了原始参数的键值格式。

按以下步骤调整即可通过验证:

  1. 手动指定正确的基准URL,剔除认证信息,确保和Twilio侧配置的回调地址完全一致(协议、域名、路径、端口都要匹配,反向代理场景不要使用内网回源地址)。
  2. 直接使用PHP原生解析的$_POST一维数组传入验证器,不要手动嵌套参数、不要转义JSON值、不要重新排序键。PHP原生$_POST默认保留了表单提交时的原始键名(即DeliveryState[0]这类格式)和原始值,不需要额外处理。

正确的验证代码如下:

use Twilio\Security\RequestValidator;

$configs = get_configs();
$signature = $_SERVER["HTTP_X_TWILIO_SIGNATURE"];
// 手动指定剔除了认证信息的正确回调地址
$request_url = "https://mysite/notification_delivery_callback.php";
$validator = new RequestValidator($configs['twilio_auth_token']);
// 直接传入原始$_POST一维数组,不要用嵌套的$_REQUEST,不要做参数转义/重构
$is_valid = $validator->validate($signature, $request_url, $_POST);

传入验证器的参数数组正确格式参考:

Array(
  [NotificationSid] => NTxxxxxxxxx,
  [ServiceSid] => ISxxxxxxxxx,
  [Count] => 4,
  [IsFinal] => true,
  [DeliveryState[3]] => {"status":"SENT","type":"sms","identity":"xxxxxxxxx","sid":"SMxxxxxxxxx"},
  [DeliveryState[2]] => {"status":"SENT","type":"sms","identity":"xxxxxxxxx","sid":"SMxxxxxxxxx"},
  [DeliveryState[1]] => {"status":"SENT","type":"sms","identity":"xxxxxxxxx","sid":"SMxxxxxxxxx"},
  [DeliveryState[0]] => {"status":"SENT","type":"sms","identity":"xxxxxxxxx","sid":"SMxxxxxxxxx"},
  [AccountSid] => ACxxxxxxxxx,
  [SequenceId] => 0
)

注意事项:

  • 所有参数保持一维结构,不要做嵌套处理
  • DeliveryState对应的子项键名保留DeliveryState[索引]的原始表单格式,不要修改索引顺序
  • DeliveryState各键对应的值直接使用收到的原始JSON字符串,不要加反斜杠转义双引号,不要提前解析成PHP数组

内容的提问来源于stack exchange,提问作者tracey armstrong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 10:12:23