迁移服务器后Filament应用Stripe Webhook遇419页面过期错误求助
Stripe Webhook 419错误排查请求
将应用迁移至新服务器后,Filament v3应用处理Stripe结账会话Webhook时持续出现问题:每次Stripe发送checkout.session.completed Webhook请求,都会返回HTTP 419错误(页面过期)。服务器访问与错误日志均记录该419错误,但Filament日志无报错信息。
环境配置
- 服务器:搭载KeyHelp面板的Ubuntu VPS,Apache服务,PHP 8.3
- SSL:通过KeyHelp管理Let’s Encrypt证书
- CDN:启用Cloudflare全DNS代理
- 应用:部署在子域名下的Filament v3
应用通过StripeController.php和自定义路由实现结账会话:用户购买商品完成结账后,Stripe通过Webhook返回结账状态,但Webhook请求始终触发419错误。
相关代码
app/Http/Controllers/StripeController.php
<?php namespace App\Http\Controllers; use App\Models\Ecg; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Stripe\Checkout\Session; use Stripe\Stripe; use Stripe\Webhook; class StripeController extends Controller { public function createCheckoutSession(Request $request) { // Setze den API-Schlüssel über die Config Stripe::setApiKey(config('services.stripe.secret')); $amount = $request->query('amount'); $ecgId = $request->query('ecg_id'); // Get the ECG ID from the query parameters $session = Session::create([ 'payment_method_types' => ['card'], 'line_items' => [[ 'price_data' => [ 'currency' => 'eur', 'product_data' => [ 'name' => 'EKG-Befundung', ], 'unit_amount' => $amount * 100, // amount in cents ], 'quantity' => 1, ]], 'mode' => 'payment', 'success_url' => route('payment.success', ['ecg_id' => $ecgId]), 'cancel_url' => route('payment.cancel'), 'metadata' => [ 'ecg_id' => $ecgId, // Store the ECG ID in metadata ], ]); return redirect()->away($session->url); } public function success(Request $request) { $ecgId = $request->query('ecg_id'); return view('payment.success', ['ecg_id' => $ecgId]); } public function cancel() { return view('payment.cancel'); } public function handleWebhook(Request $request) { Stripe::setApiKey(config('services.stripe.secret')); $endpointSecret = config('services.stripe.webhook_secret'); $payload = $request->getContent(); $sigHeader = $request->header('Stripe-Signature'); try { $event = Webhook::constructEvent($payload, $sigHeader, $endpointSecret); } catch (\UnexpectedValueException $e) { return response()->json(['error' => 'Invalid payload'], 400); } catch (\Stripe\Exception\SignatureVerificationException $e) { return response()->json(['error' => 'Invalid signature'], 400); } switch ($event->type) { case 'checkout.session.completed': $session = $event->data->object; $ecgId = $session->metadata->ecg_id; $ecg = Ecg::find($ecgId); if ($ecg) { $ecg->isPaid = true; // Update the isPaid property $ecg->save(); } break; default: Log::warning('Unhandled event type: ' . $event->type); } return response()->json(['status' => 'success']); } }
app/Http/Middleware/VerifyCsrfToken.php
<?php namespace App\Http\Middleware; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; class VerifyCsrfToken extends Middleware { /** * The URIs that should be excluded from CSRF verification. * * @var array */ protected $except = [ '/webhook', 'webhook', 'webhook/*', ]; }
routes/web.php
<?php use App\Http\Controllers\InvoiceController; use App\Http\Controllers\ReportController; use App\Http\Controllers\StripeController; use Illuminate\Support\Facades\Route; Route::get('/invoices/{id}/download', [InvoiceController::class, 'downloadPdf'])->name('invoices.download'); Route::get('/reports/{id}/download', [ReportController::class, 'downloadReportPdf'])->name('reports.download'); Route::get('/create-checkout-session', [StripeController::class, 'createCheckoutSession'])->name('create.checkout.session'); Route::get('/payment-success', [StripeController::class, 'success'])->name('payment.success'); Route::get('/payment-cancel', [StripeController::class, 'cancel'])->name('payment.cancel'); // Define the POST route for the webhook Route::post('/webhook', [StripeController::class, 'handleWebhook'])->name('webhook.handle');
.env配置
APP_ENV=production APP_KEY=xxx APP_DEBUG=true APP_TIMEZONE=Europe/Berlin APP_URL=https://app.domain.de APP_LOCALE=de APP_FALLBACK_LOCALE=de APP_FAKER_LOCALE=de_DE APP_MAINTENANCE_DRIVER=file # APP_MAINTENANCE_STORE=database STRIPE_SECRET=xxx STRIPE_WEBHOOK_SECRET=xxx BCRYPT_ROUNDS=12 LOG_CHANNEL=stack LOG_STACK=single LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug DB_CONNECTION=sqlite # DB_HOST=127.0.0.1 # DB_PORT=3306 # DB_DATABASE=laravel # DB_USERNAME=root # DB_PASSWORD= SESSION_DRIVER=cookie SESSION_LIFETIME=120 SESSION_DOMAIN=.domain.de SESSION_SAME_SITE=None SESSION_SECURE_COOKIE=true # SESSION_PATH=/ BROADCAST_CONNECTION=log FILESYSTEM_DISK=local QUEUE_CONNECTION=sync CACHE_STORE=database CACHE_PREFIX=
已尝试的解决方法
- 在
VerifyCsrfToken.php的$except数组中添加/webhook路由以排除CSRF验证 - 修改
.env中的SESSION_SAME_SITE、SESSION_DRIVER配置,验证Stripe密钥 - 配置Apache正确处理Cloudflare的
X-Forwarded-Proto等转发头 - 在Cloudflare中创建缓存规则排除
/webhook路径 - 配置Laravel会话和Cookie支持跨域请求(SameSite=None、启用安全Cookie)
- 设置
storage和bootstrap/cache文件夹权限为777 - 临时禁用KeyHelp防火墙
- 检查PHP内存限制、执行时间、POST大小等配置
- 创建并配置自定义
TrustProxies中间件以正确识别HTTPS请求
目前问题仍未解决,恳请社区提供技术建议或解决方案。
内容的提问来源于stack exchange,提问作者weiss
相关产品推荐
相关产品推荐

