PHPackages                             einenlum/cashier-paddle-webhooks - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. einenlum/cashier-paddle-webhooks

ActiveLibrary[Payment Processing](/categories/payments)

einenlum/cashier-paddle-webhooks
================================

A package to automatically listen to Paddle Webhooks in local development with Laravel Cashier

v1.0.1(10mo ago)13[4 PRs](https://github.com/Einenlum/cashier-paddle-webhooks/pulls)MITPHPPHP ^8.3CI passing

Since Aug 27Pushed 2mo agoCompare

[ Source](https://github.com/Einenlum/cashier-paddle-webhooks)[ Packagist](https://packagist.org/packages/einenlum/cashier-paddle-webhooks)[ Docs](https://github.com/einenlum/cashier-paddle-webhooks)[ GitHub Sponsors](https://github.com/Einenlum)[ RSS](/packages/einenlum-cashier-paddle-webhooks/feed)WikiDiscussions main Synced today

READMEChangelog (2)Dependencies (13)Versions (8)Used By (0)

cashier-paddle-webhooks
-----------------------

[](#cashier-paddle-webhooks)

### A package to automatically listen to Paddle Webhooks in local development with Laravel Cashier

[](#a-package-to-automatically-listen-to-paddle-webhooks-in-local-development-with-laravel-cashier)

[![Latest Version on Packagist](https://camo.githubusercontent.com/39036e46abaa33189b9f9f0304b147da47e5efffc52c93c3a277bda626e964b3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f65696e656e6c756d2f636173686965722d706164646c652d776562686f6f6b732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/einenlum/cashier-paddle-webhooks)[![GitHub Tests Action Status](https://camo.githubusercontent.com/b7560f6e01262c6e462d40f9865e6a3680dbb1bbaefee9e25662280cd409256f/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f65696e656e6c756d2f636173686965722d706164646c652d776562686f6f6b732f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/einenlum/cashier-paddle-webhooks/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/bfafa7e32c8d86c7b548ee5cf65549b25200c72f88012f5d2ce38160d2c1f44c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f65696e656e6c756d2f636173686965722d706164646c652d776562686f6f6b732f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/einenlum/cashier-paddle-webhooks/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)

This package allows you to setup Paddle webhooks locally in just one command, thanks to [tunnelmole](https://tunnelmole.com/). When the command is killed, it removes the webhook from Paddle. The library only works in *sandbox* mode.

[![](./assets/img/listen.png)](./assets/img/listen.png)

[![](./assets/img/paddle-notification.png)](./assets/img/paddle-notification.png)

This is **heavily** inspired from this [awesome command](https://github.com/lmsqueezy/laravel/blob/main/src/Console/ListenCommand.php) made by the LemonSqueezy team for their [Laravel package](https://github.com/lmsqueezy/laravel). Kudo to them! ❤️

The difference with the original package is that Paddle doesn't allow to create a webhook with a specific webhook secret. It's automatically generated by Paddle (on the contrary to LemonSqueezy). This means I had to add a few steps to make it work.

This library relies on **cache** to temporarily override the webhook secret.

Requirements
------------

[](#requirements)

- `laravel/cashier-paddle:^2.6`
- `tunnelmole` (see instructions [here](https://tunnelmole.com/))

This library does **not** work on Windows (because of signal handling).

You will also need to setup at least these envs:

```
PADDLE_SANDBOX=true
PADDLE_API_KEY=

```

Installation
------------

[](#installation)

```
composer require einenlum/cashier-paddle-webhooks
```

You should then add this in your `AppServiceProvider`:

```
use Laravel\Paddle\Cashier;

public function register()
{
    // ...
    Cashier::ignoreRoutes();
}
```

This must be done in the **register** method and not the **boot** one. See [here](https://github.com/laravel/cashier-stripe/issues/1739).

This will allow the package to override the `webhook` page that is declared by [Cashier Paddle](https://laravel.com/docs/12.x/cashier-paddle). We must override it to use our Middleware instead of Cashier's one, because we need to get the Webhook secret from the cache if it exists.

You can publish the config file with:

```
php artisan vendor:publish --tag="cashier-paddle-webhooks-config"
```

This is the contents of the published config file:

```
return [
    // Key used to store the Paddle webhook secret in the cache.
    'cache_key' => 'cashier-paddle-webhooks.secret',

    // Default is config('cashier.pash').'/webhook' if null
    // but you can override it here.
    'webhook_path' => null,

    /*
     * Whether to automatically register the webhook route
     * from this package.
     *
     * Check the documentation as to why it is enabled by default.
     *
     * If you disable this, you will have to take care yourself
     * of overriding the default Cashier Paddle middleware.
     *
     * @see VerifyPaddleWebhookMiddleware
     */
    'register_routes' => true,

    'subscribed_events' => [
        // Transaction events
        'transaction.billed',
        'transaction.canceled',
        'transaction.completed',
        'transaction.created',
        'transaction.paid',
        'transaction.past_due',
        'transaction.payment_failed',
        'transaction.ready',
        'transaction.updated',
        'transaction.revised',

        // Subscription events
        'subscription.activated',
        'subscription.canceled',
        'subscription.created',
        'subscription.imported',
        'subscription.past_due',
        'subscription.paused',
        'subscription.resumed',
        'subscription.trialing',
        'subscription.updated',

        // Product events
        'product.created',
        'product.imported',
        'product.updated',

        // Price events
        'price.created',
        'price.imported',
        'price.updated',

        // Customer events
        'customer.created',
        'customer.imported',
        'customer.updated',

        // Payment method events
        'payment_method.saved',
        'payment_method.deleted',

        // Address events
        'address.created',
        'address.imported',
        'address.updated',

        // Business events
        'business.created',
        'business.imported',
        'business.updated',

        // Adjustment events
        'adjustment.created',
        'adjustment.updated',

        // Payout events
        'payout.created',
        'payout.paid',

        // Discount events
        'discount.created',
        'discount.imported',
        'discount.updated',

        // Discount group events
        'discount_group.created',
        'discount_group.updated',

        // Report events
        'report.created',
        'report.updated',

        // API key events
        'api_key.created',
        'api_key.expired',
        'api_key.expiring',
        'api_key.revoked',
        'api_key.updated',

        // Client token events
        'client_token.created',
        'client_token.updated',
        'client_token.revoked',
    ],
];
```

Usage
-----

[](#usage)

```
php artisan cashier-paddle-webhooks:listen
```

This will create a tunnel thanks to `tunnelmole` which listens to `http://localhost:8000`, create a webhook on Paddle sandbox, fetch its id and secret, and set the secret into cache (whatever your cache setup is). When the command is killed, it removes the webhook from Paddle and removes the key from the cache.

Then in the [VerifyPaddleWebhookMiddleware](src/Http/Middlewares/VerifyPaddleWebhookMiddleware.php), the cached secret key is used if it exists and if you are not in production.

You can change the port with

```
php artisan cashier-paddle-webhooks:listen --port 1234
```

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Credits
-------

[](#credits)

- [Dries Vints](https://github.com/driesvints) for his great library [lmsqueezy/laravel](https://github.com/lmsqueezy/laravel)
- [Spatie](https://spatie.be/) team for creating the [spatie/package-skeleton-laravel](https://github.com/spatie/package-skeleton-laravel) template

Other projects of mine
----------------------

[](#other-projects-of-mine)

- [human-replay](https://github.com/Einenlum/human-replay): Make text appear exactly how you typed it. Copy paste a few JS lines.
- [tailwind-alchemist](https://github.com/Einenlum/tailwind-alchemist): A CLI tool to find and replace Tailwind CSS colors 🪄
- [yaml-revealer](https://github.com/Einenlum/yaml-revealer): A vim plugin to easily navigate through Yaml files

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance72

Regular maintenance activity

Popularity5

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 94.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

311d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/5675200?v=4)[Yann Rabiller](/maintainers/Einenlum)[@einenlum](https://github.com/einenlum)

---

Top Contributors

[![einenlum](https://avatars.githubusercontent.com/u/5675200?v=4)](https://github.com/einenlum "einenlum (48 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

cashierlaravelpaddlepaymentwebhookslaravelEinenlumcashier-paddle-webhooks

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/einenlum-cashier-paddle-webhooks/health.svg)

```
[![Health](https://phpackages.com/badges/einenlum-cashier-paddle-webhooks/health.svg)](https://phpackages.com/packages/einenlum-cashier-paddle-webhooks)
```

###  Alternatives

[spatie/laravel-permission

Permission handling for Laravel 12 and up

12.9k102.4M1.4k](/packages/spatie-laravel-permission)[spatie/laravel-pdf

Create PDFs in Laravel apps

1.0k4.8M47](/packages/spatie-laravel-pdf)[dedoc/scramble

Automatic generation of API documentation for Laravel applications.

2.1k11.2M100](/packages/dedoc-scramble)[spatie/laravel-passkeys

Use passkeys in your Laravel app

471890.7k39](/packages/spatie-laravel-passkeys)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3914.6k](/packages/rawilk-profile-filament-plugin)[wnx/laravel-backup-restore

A package to restore database backups made with spatie/laravel-backup.

213421.0k2](/packages/wnx-laravel-backup-restore)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
