Laravel Cashier + Paddle: Handling Guest Checkouts

Let users buy without logging in. Start a Paddle guest checkout in Laravel and create the user on webhook so Cashier can process subscriptions and payments.

Laravel Cashier + Paddle: Handling Guest Checkouts

If you’re using Laravel Cashier with the Paddle integration, you might have noticed a quirk when it comes to guest checkouts. While it is mentioned in the docs that you can start a guest checkout session, it’s not very clear on how to handle the process after the checkout is completed.

And by default, Cashier expects the user to already exist when processing the webhook from Paddle. If the user doesn’t exist, the webhook processing fails silently, and the transaction isn’t recorded.

In this post, I’ll show you how to handle guest checkouts properly by creating the user when the webhook is received, allowing Cashier to process the transaction as expected.

The User Experience

But first, let’s clarify the user experience I wanted to achieve:

ux

No account needed upfront. The user clicks “Buy now,” goes through Paddle’s checkout as a guest, and after completing the purchase, they get an account created for them automatically - because they provided their email during checkout.

The Solution

The key is to create the user when the webhook from Paddle is received, before Cashier tries to process it. We can achieve this by listening for the WebhookReceived event and handling the user creation logic there. This way, when Cashier processes the webhook, the user already exists.

Starting the Checkout

When starting the checkout, use the guest method to initiate a guest checkout session. This tells Paddle that the user will provide their email during the checkout process.

// For guest users - create checkout without customer data
// Paddle will collect email/customer info during checkout
$checkout = \Laravel\Paddle\Checkout::guest([
              ['priceId' => $priceId, 'quantity' => 1]
            ])->returnTo(route('checkout.success'));

This will create the checkout session and lets you redirect the user to Paddle’s hosted checkout page.

The Listener

I added a listener for WebhookReceived that runs before Cashier does its normal processing. This listener looks at the webhook payload, fetches the customer data from Paddle, and either links it to an existing user or creates a new one. Here’s the complete listener code:

Registering the Listener

In your app/Providers/AppServiceProvider.php, make sure the listener runs before Cashier’s own webhook handler:

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        // Handle WebhookReceived first to create users for guest checkouts
        Event::listen(WebhookReceived::class, HandleWebhookReceived::class);

        // Then handle TransactionCompleted for license creation
        Event::listen(TransactionCompleted::class, HandleTransactionCompleted::class);
    }

Notes & gotchas

  • Webhook order & idempotency. Paddle may retry webhooks. The listener is safe to run multiple times (we check/link first).
  • Email as the key. I match on the Paddle customer’s email. If your product allows multiple accounts with the same email, adjust accordingly.
  • Password & onboarding. I generate a random password and mark the email verified. After redirecting to the success page, send a “set your password” link or show an inline prompt to finish account setup.
  • Security. Keep Paddle’s webhook signature validation enabled. This listener doesn’t change that.
  • Return URLs. ->returnTo(route(‘checkout.success’)) should render a friendly “You’re all set” and nudge people into the app.

Wrapping Up

With this in place, users can click “Buy now,” go through Paddle as guests, and when the webhook comes in, your app creates the user automatically. That way, Cashier has everything it needs to finish processing the transaction.

It feels a little strange that Cashier supports starting guest checkouts but not finishing them. Until that gap is filled, this approach works well and keeps the user experience smooth.

Happy coding! 🚀