Certainly! Here's a checklist and solution steps for why your Stripe webhook might not be firing your Laravel event, even though your CLI and HTTP 200 responses look good.
1. Confirm Stripe CLI is Forwarding to the correct URL
Make sure Stripe CLI is forwarding events to the webhook that your Laravel app is listening on:
stripe listen --forward-to http://myproject.test/stripe/webhook
2. Verify the Route
Check that the /stripe/webhook route exists and is handled by the appropriate controller. With Laravel Cashier, the webhook is usually set up like this:
// routes/web.php OR routes/api.php
use Laravel\Cashier\Http\Controllers\WebhookController;
Route::post('/stripe/webhook', [WebhookController::class, 'handleWebhook']);
OR if you have customized it:
Route::post('/stripe/webhook', [App\Http\Controllers\StripeWebhookController::class, '__invoke']);
Make sure there's only one route for Stripe webhook and it’s not protected by auth or CSRF middleware.
3. Check Stripe Webhook Secret
In your .env file, ensure the secret is set:
STRIPE_WEBHOOK_SECRET=whsec_XXXXXXXXX
And in config/cashier.php:
'webhook' => [
'secret' => env('STRIPE_WEBHOOK_SECRET'),
// ...
],
4. Listener Registration
From your event:list, it looks like the listener is registered properly (e.g., App\Listeners\StripeEventListener@handle). Confirm your event and listener names are correct and match what's being fired.
5. Debug the Webhook Handler
Add a quick log at the start of your listener or webhook controller:
// In your handler/controller:
\Log::info('Stripe webhook received', request()->all());
If you don't see this in your logs, your controller isn't being hit at all.
6. Stripe Event Handling
If using Laravel Cashier, it fires Laravel\Cashier\Events\WebhookReceived and others for Stripe webhooks.
Check your EventServiceProvider:
protected $listen = [
\Laravel\Cashier\Events\WebhookReceived::class => [
\App\Listeners\WebhookReceived::class,
],
// or your custom event/handler
];
7. Return a 200 Response
Your webhook controller must return a 200 HTTP response even for unhandled events, or Stripe will retry. The Cashier controller does this by default.
8. CSRF Exclusion
If you placed your webhook in web.php, be sure to exclude it from CSRF in VerifyCsrfToken middleware:
protected $except = [
'stripe/webhook',
];
9. Queued Listeners
If your listener is queued and the queue worker isn't running, the logic won’t execute:
php artisan queue:work
Simplified Checklist
- Webhook Route exists and is public (no auth or CSRF).
- Stripe CLI forwards to correct local URL.
- Listener is registered and not queued (test sync first).
-
.envhas the correct STRIPE_WEBHOOK_SECRET. - Controller/Listener logs confirm it’s being hit.
Example Minimal Setup
routes/web.php:
use Laravel\Cashier\Http\Controllers\WebhookController;
Route::post('/stripe/webhook', [WebhookController::class, 'handleWebhook']);
App\Providers\EventServiceProvider.php:
protected $listen = [
\Laravel\Cashier\Events\WebhookReceived::class => [
\App\Listeners\WebhookReceived::class,
],
];
App\Listeners\WebhookReceived.php:
namespace App\Listeners;
use Laravel\Cashier\Events\WebhookReceived;
class WebhookReceived
{
public function handle(WebhookReceived $event)
{
\Log::info('WebhookReceived event:', $event->payload);
// Handle your logic here...
}
}
If the log at the start of your handler never appears:
- Your route/webhook controller is incorrect
- Stripe CLI is forwarding to a wrong/mistyped URL
- CSRF/auth is blocking
- There's a typo in your EventServiceProvider registration
If logging works but your cart logic doesn’t:
- Check your listener’s logic
- Check your queue status (if listener is queued)
If you've checked all the above and still have trouble, please post your route and controller code for further diagnosis!