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、签名头和密钥,方便本地验证:
可以用Stripe官方的签名验证逻辑本地测试这些参数,定位问题所在。error_log('Raw Payload: ' . $payload); error_log('Stripe Signature: ' . $sig_header); error_log('Endpoint Secret: ' . $endpoint_secret); - 确认Webhook事件订阅:登录Stripe后台,检查Webhook端点是否勾选了需要处理的事件类型(比如
payment_intent.succeeded),未订阅的事件不会发送。
内容的提问来源于stack exchange,提问作者ray.php
相关产品推荐
相关产品推荐

