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
相关产品推荐
相关产品推荐

