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

PHP中无法匹配GoCardless Webhook签名:计算值与头部签名不一致

GoCardless Webhook签名匹配失败问题

我尝试将GoCardless Webhook提供的签名与PHP中生成的计算签名进行匹配,但使用以下代码始终无法让$computed_signature与$signature_header匹配:

$webhook_endpoint_secret = getenv("Secret");
$request_body = file_get_contents('php://input');

$request_body_clean = preg_replace("/(\s{2,}|\t|\r|\n)/i",'',$request_body);

$headers = getallheaders();
$signature_header = $headers["Webhook-Signature"];

$payload = json_decode($request_body, true);

$computed_signature = hash_hmac("sha256", $request_body_clean, $webhook_endpoint_secret);

var_dump ($signature_header);
var_dump ($computed_signature);

我已尝试移除preg_replace(推测GoCardless发送的已是压缩内容),也调换过hash_hmac的参数顺序,但均无法得到正确匹配结果。目前其他功能正常,可获取Webhook信息,仅签名匹配失败,恳请技术帮助。


问题排查与解决方案

以下是关键的排查点和修复方案:

  • 禁止修改原始请求体:GoCardless的签名基于原始未修改的请求正文生成,任何对空格、换行的修改都会导致签名不匹配。直接使用$request_body作为hash_hmac的输入,完全移除$request_body_clean相关代码。

  • 正确解析签名头格式:GoCardless的Webhook-Signature头格式为t=<时间戳>,v1=<签名值>,你需要提取v1=后的签名部分,而非直接用整个头内容对比。示例代码:

    // 解析签名头,提取v1对应的签名值
    $actual_signature = '';
    $signature_parts = explode(',', $signature_header);
    foreach ($signature_parts as $part) {
        $part = trim($part);
        if (str_starts_with($part, 'v1=')) {
            $actual_signature = substr($part, 3);
            break;
        }
    }
    
  • 验证密钥一致性:检查getenv("Secret")获取的密钥是否和GoCardless后台配置的Webhook密钥完全一致,注意不要包含多余的空格或换行符。

  • 排查中间件篡改风险:确认服务器或中间件(如Nginx、Apache)没有自动解析或格式化请求体,可将$request_body写入日志,和GoCardless发送的原始请求体对比,确保内容完全一致。

修改后的完整验证代码:

$webhook_endpoint_secret = getenv("Secret");
$request_body = file_get_contents('php://input');

$headers = getallheaders();
$signature_header = $headers["Webhook-Signature"];

// 解析签名头获取实际签名值
$actual_signature = '';
$signature_parts = explode(',', $signature_header);
foreach ($signature_parts as $part) {
    $part = trim($part);
    if (str_starts_with($part, 'v1=')) {
        $actual_signature = substr($part, 3);
        break;
    }
}

// 使用原始请求体计算签名
$computed_signature = hash_hmac("sha256", $request_body, $webhook_endpoint_secret);

// 用hash_equals避免时序攻击,对比签名
if (hash_equals($computed_signature, $actual_signature)) {
    // 签名验证通过,处理业务逻辑
    $payload = json_decode($request_body, true);
} else {
    // 签名验证失败,拒绝请求
    http_response_code(403);
    exit("Invalid signature");
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 11:20:07