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

WooCommerce Webhook返回401错误 认证头应在PHP代码何处添加

WooCommerce Webhook接收端401错误修复方案

首先明确核心逻辑:你是Webhook推送的接收方,不需要主动给请求添加Authentication Headers。认证头是WooCommerce向你的地址发POST请求时主动附带的,你需要做的是在接收逻辑最开头校验这个签名,而非自己构造请求头。

你日志里看到的woocommerce_rest_cannot_view错误不是你写的PHP代码返回的,是请求被拦截后转到WordPress REST接口返回的内容,说明请求根本没走到你写的业务逻辑里。

401错误排查清单

按顺序排查以下问题,覆盖90%以上的同类场景:

  • Webhook投递URL配置错误:不要填成WooCommerce站点自身的REST API路径,必须填你写的PHP接收文件对应的公网可访问HTTPS地址
  • 接收地址被访问控制拦截:包括站点开启了HTTP Basic Auth目录密码保护、配置了IP白名单、CDN/WAF规则拦截POST请求、安装了REST API禁用类插件,都会导致请求被拦截返回401
  • Webhook密钥配置异常:检查WooCommerce后台Webhook编辑页的「Secret」字段,确认已经生成有效值,不要留空
  • 服务器过滤请求头:部分Web服务器配置会过滤非标准HTTP头,导致代码拿不到WooCommerce附带的签名头
  • HTTPS证书异常:确保接收端的HTTPS证书是正规CA签发的有效证书,不要用自签名证书,否则WooCommerce的投递请求会被拦截

正确的签名验证实现

把签名验证逻辑放在所有业务代码最前面,校验通过后再处理订单数据,修复后的完整代码如下:

<?php
session_start();
include "../db_conn.php";
include "../config.php";
include "../functions.php";

// ---------------- Webhook签名验证逻辑开始 ----------------
// 替换成你WooCommerce Webhook配置页生成的Secret密钥
$webhook_secret = '填写你自己的Webhook Secret值';

// 兼容不同服务器环境的请求头获取方法
if (!function_exists('getallheaders')) {
    function getallheaders() {
        $headers = [];
        foreach ($_SERVER as $name => $value) {
            if (substr($name, 0, 5) == 'HTTP_') {
                $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
            }
        }
        return $headers;
    }
}
$all_headers = getallheaders();
$signature = $all_headers['X-WC-Webhook-Signature'] ?? '';

// 读取原始请求体,必须使用未经过任何过滤转义的原始内容计算签名
$raw_payload = file_get_contents('php://input');
// 用密钥计算预期签名
$expected_signature = base64_encode(hash_hmac('sha256', $raw_payload, $webhook_secret, true));

// 签名校验不通过直接拒绝请求
if (!hash_equals($expected_signature, $signature)) {
    http_response_code(403);
    exit('Invalid webhook signature');
}
// ---------------- 签名验证逻辑结束 ----------------

// 校验通过后再解析数据处理业务
if($json = json_decode($raw_payload)) {
  $order = $json;
  file_put_contents("request_confirm.txt","request arrived");

  if($order->status == "completed")
  {
    $purchased_hours=0;
    foreach ($order->line_items  as $line_item)
    {
      if($line_item->sku != "")
      {
        $hours=$line_item->sku * $line_item->quantity;
        $purchased_hours+=$hours;
      }
      else
      {
        $purchased_hours+=$line_item->quantity;
      }
    }
    $purchased_at=$order->date_created;
    $user=$order->billing->first_name." ".$order->billing->last_name;
    $order_id=$order->id;

    $data="Purchased_hours: ".$purchased_hours." Cutomer name: ".$user." purchased_at: ".$purchased_at;
    $purchased_seconds=$purchased_hours*3600;
    try{
      // 修复原代码SQL注入风险,所有参数改用预处理绑定
      $sql = "INSERT INTO user_hours (purchased_seconds, name, purchased_at, order_id) SELECT ".
        "?,?,?,? WHERE NOT EXISTS (SELECT 1 FROM user_hours".
        " WHERE order_id = ?)";
      $insert= $pdo->prepare($sql);
      $insert->execute([$purchased_seconds, $user, $purchased_at, $order_id, $order_id]);
    }
    catch (\Exception $exception)
    {
      file_put_contents("error.txt",$exception." order number: ".$order->id);
    }

  }
  $txt = "<pre>".print_r($order, true)."</pre>";
  file_put_contents("test_webhook.txt",$txt);
  file_put_contents("test_webhook_collected_data.txt",$data);
  // 处理完成必须返回200状态码,否则WooCommerce会判定投递失败反复重试
  http_response_code(200);
  exit;

} else {
  http_response_code(400);
  exit('Invalid request payload');
}
?>

额外注意事项

  • 业务逻辑处理完成后必须返回200状态码,不要输出无关内容,否则WooCommerce会启动重试机制,默认最多重试15次
  • 原代码中直接把$order_id拼接进SQL语句存在SQL注入风险,上述代码已经修正为全参数预处理绑定,直接替换即可
  • 如果使用Nginx作为Web服务器,确认配置中没有过滤非标准HTTP头的规则,必要时可以添加fastcgi_param HTTP_X_WC_WEBHOOK_SIGNATURE $http_x_wc_webhook_signature;配置透传签名头
  • 测试时可以先把签名验证逻辑临时注释,确认请求能正常到达代码、能正确写入日志,再开启签名校验,方便定位问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 01:48:34