liberusoftware/webhooks-api
Laravel package for building webhook APIs: register endpoints, dispatch payloads with retries, verify signed requests, and track delivery logs. A clean foundation for third-party integrations with configurable queues, events, and migrations.
composer require liberusoftware/webhooks-api
php artisan vendor:publish --tag=webhooks-api-config # verify tag name in the provider
php artisan migrate
php artisan route:list --path=webhooks
The package is a presentation adapter, not a full webhook engine. Expect it to ship HTTP routes, controllers, form requests, API resources, and a service provider that binds those controllers to interfaces owned by a separate domain/application module. Start by reading, in this order:
composer.json — the require block tells you which core module (liberusoftware/module-webhooks or similar) supplies the contracts. That package is where the real behaviour lives; this one is the HTTP skin.src/Providers/*.php — look at register()/boot() to see which interfaces are bound to which implementations, whether routes/migrations/config are published, and what middleware the route group uses.routes/ — the actual surface area. Note the prefix, the api middleware group, and whether auth (Sanctum/Passport/session) is applied or left to you.config/ — guard, route prefix, table names, pagination.First use case: register a subscriber endpoint from a client application (POST /api/webhooks with url, events[], and whatever secret field the request validates), then trigger one of the listed events in your app and confirm a signed delivery lands on a request-bin URL. That single loop exercises routing, auth, validation, persistence, event fan-out, queued dispatch, and signing — if it works end to end, you understand the package.
Because stars and dependents are effectively zero, treat the version you install as a fork you own. Pin it exactly ("liberusoftware/webhooks-api": "1.0.*" or a commit hash) and read the source before building on it.
Adapter over ports. Do not call the package's Eloquent models from your own controllers. Resolve the domain contract instead and let this adapter handle transport:
public function __construct(private WebhookSubscriptions $subscriptions) {}
If a binding doesn't suit you (custom repository, tenancy scoping, audit logging), rebind it in your own AppServiceProvider::register() after the package provider — later bindings win. That is the primary extension point and it keeps you off the vendor code.
Event → webhook fan-out. The idiomatic shape is a listener per domain event that maps to the package's dispatch API:
Event::listen(OrderShipped::class, DispatchWebhook::class);
Keep the mapping table (OrderShipped → order.shipped) in your own config, not in the package, so event names stay stable when the vendor renames things.
Auth and ownership. The adapter usually validates payloads but rarely knows who owns a subscription. Put the authorize step in a middleware or policy layer keyed on user_id/tenant_id, and always scope list/show/delete queries by the authenticated principal. For machine-to-machine consumers, Sanctum tokens with an ability like webhooks:manage is the cleanest fit.
Delivery pipeline. Wire deliveries to a queue (ShouldQueue on the listener or the job the package dispatches), with per-subscription retry/backoff config, a hard timeout, and signature headers (X-Webhook-Signature + timestamp) so consumers can reject replays. Persist delivery attempts with response code and body snippet — you will need them the first time a customer says "we never got it."
Testing. Event::fake() to assert dispatch intent, Http::fake() to assert the outgoing payload and headers, and Queue::fake() to keep tests fast. Write one contract test that asserts the JSON shape of a subscription resource; that is the thing most likely to break on a minor bump.
Integration tip: if you run a modular monolith, mount this adapter only in the API-facing app/route file, and keep the domain module free of HTTP concerns. That is the whole point of the split and it makes swapping the adapter later a one-line composer change.
extra.laravel.providers in composer.json. If it's absent, register the provider manually in bootstrap/providers.php (Laravel 11+) or config/app.php./api/webhooks prefix will clash with anything you already expose. Override the prefix in the published config before writing client code, and re-run route:list after every upgrade.events array, a soft-deleted owner, or an event fired before the transaction commits. Use DB::afterCommit() / ShouldDispatchAfterCommit on listeners so you don't ship webhooks for rolled-back orders.php artisan route:list --path=webhooks, then Log::channel('single') around the dispatch job; a stalled queue worker looks identical to a broken adapter. Check failed_jobs first.vendor/.src/Http directory between versions rather than trusting a changelog; request-validation and response-shape changes are the usual breaking surface.How can I help you explore Laravel packages today?