Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

lat4732's avatar
Level 12

Why Doesn't Laravel Cashier Handle My Webhook Logic Automatically Despite Logging Valid Webhooks?

I'm integrating Paddle with Laravel using Cashier (Paddle), and I've run into an issue where my webhook logic doesn't seem to trigger the automatic database updates I expect from Cashier.

What Works

I can confirm that paddle webhooks are being received and logged correctly using a listener:

<?php

namespace App\Lib\Account\Listeners;

use Illuminate\Support\Facades\Log;
use Laravel\Paddle\Events\WebhookReceived;

class PaddleWebhookListener
{
    /**
     * Handle the event.
     */
    public function handle(WebhookReceived $event): void
    {
        $payload = $event->payload;

        if (empty($payload['event_type'])) {
            Log::warning('Webhook payload does not contain event type.', ['payload' => $payload]);
            return;
        }

        Log::debug('Paddle Webhook: ' . $payload['event_type'], ['payload' => $payload]);
    }
}

I see debug logs for all the expected event types (e.g., subscription.created, transaction.completed), so I know Paddle is sending the webhooks correctly, and they’re reaching my app.

What Doesn't Work

Cashier doesn't seem to be automatically handling these webhooks to update the database (e.g., creating customers, recording transactions, managing subscriptions). I expected Cashier to handle most of this logic out of the box like it does in Stripe implementation.

Configuration

  • Route /paddle/webhook is excluded from CSRF verification
  • Environment variables are correctly set in the .env file
  • Cashier migrations are in place, and the database tables (customers, subscriptions, subscription_items, transactions) exist.

Question

Why isn't Cashier automatically handling these webhooks as expected? Do I need to manually implement logic for all the event types like subscription_created and transaction_completed?

Any guidance would be greatly appreciated!

0 likes
2 replies
jamesbuch's avatar

I think the key thing here is whether you are overriding the default Cashier webhook handling or not. If you are doing it all yourself, then yes, you handle all the logic yourself. You handle updating all the models and grabbing what you want out of the event payload.

My reading is that Cashier will handle all the common Paddle event's automatically, but if you specify your own webhook handler URL in .env, then Cashier's default logic isn't going to run, because that's how you would override the default Cashier behavior. Or at least I think that's what the documentation is saying:

https://laravel.com/docs/11.x/cashier-paddle#defining-webhook-event-handlers

From the documentation:

You can also override the default, built-in webhook route by defining the CASHIER_WEBHOOK environment variable in your application's .env file.

So you see, as far as I can tell, you might be overriding it and so not getting the default behavior.

If you leave out this CASHIER_WEBHOOK and just use the listener, then you should get the default behavior and be able to handle other events by listening as you are doing in your handler.

For reference, here is what's happening in Cashier's default handler:

https://github.com/laravel/cashier-paddle/blob/2.x/src/Http/Controllers/WebhookController.php

You can see how it dispatches based on event types, what they are, what models are updated and with what data and so on. So if you want to override, copy and paste in this code if you like from Cashier's default code, and add your own logic if you really want to. Otherwise, remove the CASHIER_WEBHOOK in .env and don't override, just listen for other events and use the source code as a guide to what you may wish to do with events Cashier doesn't automatically handle.

Hope this helps! I find this interesting because I'm about to try and integrate Paddle myself, after years of handling Stripe and finding Paddle to be a better alternative for my needs now.

1 like
lat4732's avatar
Level 12

@jamesbuch Hey, I figured out the issue! The problem lies in the WebhookController.php of the cashier-paddle package. Specifically, in the handleSubscriptionCreated method:

It simply returns whenever something doesn't align with its expectations.

I fixed the issue by creating the user as a customer before opening a Paddle portal for checkout:

$request->user()->createAsCustomer();

Please or to participate in this conversation.