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

Codeigniter 3中Stripe Webhook签名验证失败,求排查思路

Stripe Webhook签名验证失败排查思路(Codeigniter 3环境)

问题背景

我正在Codeigniter 3中开发一款基于订阅模式的脚本,创建结账会话功能可正常运行:

private function create_checkout_session($type = "monthly", $productID, $metadata = array()) {
        if (!$productID) {
            die("Missing product ID");
        }

        if ($type == "monthly") {
            $mode = "subscription";
        } else if ($type == "yearly") {
            $mode = "payment";
        } else {
            $mode = "payment";
        }

        // Set your success and cancel URLs
        $success_url = 'https://xxxxxxx.com/memberships';
        $cancel_url =  'https://xxxxxxx.com/memberships';

        // Initialize the metadata array
        $metadata_array = [];

        // Process the metadata from the $metadata variable using foreach loop
        foreach ($metadata as $key => $value) {
            $metadata_array[$key] = $value;
        }

        try {
            $session = \Stripe\Checkout\Session::create([
                'payment_method_types' => ['card'],
                'line_items' => [[
                    'price' => $productID, // Replace with your product price ID
                    'quantity' => 1,
                ]],
                'automatic_tax' => [
                    'enabled' => true,
                ],
                'metadata' => $metadata_array, // Set the processed metadata array here
                'mode' => $mode,
                'success_url' => $success_url,
                'cancel_url' => $cancel_url,
            ]);

            // Redirect the customer to the checkout session URL
            redirect($session->url);

        } catch (\Stripe\Exception\ApiErrorException $e) {
            // Handle any errors that occur during the session creation
            echo "Error: " . $e->getMessage();
        }
    }

但使用测试卡号4242 4242 4242 4242完成支付后,Webhook始终返回「无效签名」,Webhook处理函数如下:

public function stripe_webhook() {
    $endpoint_secret = 'we_1Nbi11111111111111';

    $payload = @file_get_contents('php://input');
    $event = null;

    try {
      $event = \Stripe\Event::constructFrom(
        json_decode($payload, true)
      );
    } catch(\UnexpectedValueException $e) {
      // Invalid payload
      echo '⚠️  Webhook error while parsing basic request.';
      http_response_code(400);
      exit();
    }
    if ($endpoint_secret) {
      // Only verify the event if there is an endpoint secret defined
      // Otherwise use the basic decoded event
      $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
      try {
        $event = \Stripe\Webhook::constructEvent(
          $payload, $sig_header, $endpoint_secret
        );
      } catch(\Stripe\Exception\SignatureVerificationException $e) {
        // Invalid signature
        echo '⚠️  Webhook error while validating signature.';
        http_response_code(400);
        exit();
      }
    }

    // Handle the event
    switch ($event->type) {
      case 'payment_intent.succeeded':
        $paymentIntent = $event->data->object; // contains a \Stripe\PaymentIntent
        // Then define and call a method to handle the successful payment intent.
        // handlePaymentIntentSucceeded($paymentIntent);
        break;
      case 'payment_method.attached':
        $paymentMethod = $event->data->object; // contains a \Stripe\PaymentMethod
        // Then define and call a method to handle the successful attachment of a PaymentMethod.
        // handlePaymentMethodAttached($paymentMethod);
        break;
      default:
        // Unexpected event type
        error_log('Received unknown event type');
    }

    http_response_code(200);
}

已多次核对测试密钥与Webhook密钥,仍无法解决问题,求排查思路。


排查思路

  • 密钥环境必须严格匹配:测试模式下必须使用Stripe后台生成的测试版Webhook密钥(we_开头的测试密钥),绝对不能混用生产密钥,反之亦然。
  • 禁止提前解析Payload:你的代码中先调用Event::constructFrom解析了Payload,这会修改原始请求体的结构,导致后续签名验证失败。正确流程是先验证签名,再解析事件,修正后的核心代码如下:
    $payload = @file_get_contents('php://input');
    $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
    $event = null;
    
    try {
      $event = \Stripe\Webhook::constructEvent(
        $payload, $sig_header, $endpoint_secret
      );
    } catch(\Stripe\Exception\SignatureVerificationException $e) {
      echo '⚠️  Webhook error while validating signature.';
      http_response_code(400);
      exit();
    } catch(\UnexpectedValueException $e) {
      echo '⚠️  Webhook error while parsing basic request.';
      http_response_code(400);
      exit();
    }
    
  • 关闭Codeigniter的XSS过滤:Codeigniter默认的全局XSS过滤会修改原始Payload内容,导致签名不匹配。可以在Webhook方法开头添加:
    $this->input->enable_xss(FALSE);
    
    或者在config.php中针对Webhook路由单独关闭XSS过滤。
  • 确保签名头正确获取:部分服务器环境会修改HTTP头的命名格式,改用getallheaders()获取签名头更可靠:
    $headers = getallheaders();
    $sig_header = $headers['Stripe-Signature'] ?? '';
    
  • 检查中间件是否修改请求:如果你的Webhook地址经过CDN、WAF或其他中间件,确认这些服务没有修改请求体或请求头,否则会破坏签名验证。
  • 添加调试日志:在Webhook方法中记录原始Payload、签名头和密钥,方便本地验证:
    error_log('Raw Payload: ' . $payload);
    error_log('Stripe Signature: ' . $sig_header);
    error_log('Endpoint Secret: ' . $endpoint_secret);
    
    可以用Stripe官方的签名验证逻辑本地测试这些参数,定位问题所在。
  • 确认Webhook事件订阅:登录Stripe后台,检查Webhook端点是否勾选了需要处理的事件类型(比如payment_intent.succeeded),未订阅的事件不会发送。

内容的提问来源于stack exchange,提问作者ray.php

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 18:15:56