opscale-co/nova-webhooks
opscale-co/nova-webhooks adds webhook management to Laravel Nova. Create and manage outgoing webhook endpoints, configure events, and monitor deliveries from your Nova admin panel. A straightforward tool for integrating webhook support into Laravel applications.
Install and wire it up
composer require opscale-co/nova-webhooks
php artisan vendor:publish --provider="Opscale\NovaWebhooks\NovaWebhooksServiceProvider"
php artisan migrate
Then register the tool/resource in app/Nova/NovaServiceProvider.php (tools() or resources()) unless the package auto-discovers it. Confirm the required PHP, Laravel, and Nova majors against the package's composer.json before you commit — the changelog does not state whether v1.2.0 raises any version floors.
Where to look first, in order
config/nova-webhooks.php (after publishing) — queue connection, signing secret, retry/timeout, event mappings. This is the file that answers 80% of your questions.opscale-co/actions directly, its v4 upgrade notes — v1.2.0's only change is adopting actions v4.First use case (do this before anything else)
Create one webhook endpoint pointing at a request bin (webhook.site, ngdst, or php artisan serve + a dump route). Subscribe it to created on a low-risk test model. Set the signing secret. Confirm a queue worker is running. Create a record. Inspect the delivery log — status code, payload body, headers. That single loop validates config, queue, event wiring, serialization, and signing all at once.
Do not start by wiring up a production consumer. You need to see one payload first.
Endpoint-per-system, not one catch-all
Create one webhook row per external system per environment. A single "send everything to one URL" endpoint forces the receiver to route by event type, duplicates retry/backoff semantics across unrelated consumers, and makes disabling one integration impossible without disabling all of them.
Use the active/inactive toggle as your rollout mechanism
This is the package's main day-to-day operational lever. Register the endpoint inactive, deploy the consumer, verify it can accept and validate a payload, then flip it on in Nova. If the consumer breaks in production, flip it off — no redeploy, no queue drain, no stuck jobs. This is the reason to keep endpoint config in the database rather than in config files.
Keep secrets out of the database where you can
The Nova UI is convenient for URLs and event subscriptions. Signing secrets are the one thing worth sourcing from environment/secret manager if the package supports it. If it only stores secrets in the DB, then treat DB read access as equivalent to secret read access and gate accordingly (see Gotchas).
Subscribe narrowly
Prefer explicit created/updated/deleted on the specific models that matter. Broad subscriptions multiply outbound requests by fan-out: one internal event with N subscribers is N HTTP calls and N log rows. On a high-write model that is your queue backlog and your storage bill, not the webhook package's problem to solve for you.
Normalize the payload at the source
Webhook payloads are a public contract with an external team. If the package supports payload transformers or custom resources, put serialization there — never ship raw $model->toArray(). Raw attributes leak internal column names, expose fields you added later without thinking, and change shape every time you touch a migration. A stable, deliberately-versioned payload is worth the small amount of extra code.
Trigger from domain actions, not controllers
If you are dispatching webhooks from your own code, do it from a service class or action so the same event fires regardless of entry point (HTTP, console, queue, Nova action). With opscale-co/actions v4 in the dependency graph, align your action signatures with the contract the package expects — a mismatch here surfaces as silently-missing deliveries, not an exception.
Test with fakes, at the right layer
Queue::fake() if the package dispatches a job — assert the job was pushed, not that HTTP happened. This is the fast test.Http::fake() if delivery is synchronous or you are testing the job in isolation — assert URL, headers, and signature header.Http::fake() with a sequenced 500-then-200 response and run the job directly a few times.Multi-tenant scoping
If you run multi-tenant, scope both endpoints and delivery logs by tenant. The dangerous failure here is a shared endpoint whose payload for tenant A gets delivered to a consumer that assumes tenant B — or worse, a delivery log that lets a tenant admin read payloads containing another tenant's data.
v1.2.0 is a dependency bump. Treat it as one.
The release note says only "adopt opscale-co/actions v4." That is a major bump of the dispatch/execution layer inside a minor package release. The changelog does not state whether behavior changed. Before upgrading, diff composer.json between v1.1.0 and v1.2.0 — if the only diff is the actions constraint, you have bounded the risk in fifteen minutes. If the PHP/Laravel/Nova floors also moved, you have a decision to make.
Practical exposure: retry/backoff defaults, job payload shape, and middleware all live in that dependency. Those are exactly the behaviors a smoke test will not catch. Run v1.2.0 in a branch with a receiver that deliberately returns 4xx and 5xx, and watch the retry pattern before merging.
"Nothing happened" is almost never the package
Debug in this order: (1) Is the event actually subscribed? (2) Is the webhook active? (3) Is a queue worker running with the right connection? (4) Is the job in failed_jobs? (5) What does the delivery log say? Nine times out of ten it is step 3.
Config changes need a restart, not a save
php artisan config:clear
php artisan queue:restart
Long-running workers hold the old config in memory. Changing the queue connection or signing secret and then wondering why nothing changed is the single most common self-inflicted wound with any webhook package.
Assume at-least-once delivery
Verify with your consumer team that duplicates are tolerated. If the package does not emit a delivery ID or idempotency key header, add one via a payload transformer or header extension point — retrofitting idempotency into a consumer after it has processed duplicates in production is expensive. Retry semantics are exactly the thing the actions v4 bump could have altered, so re-verify after upgrading.
Rotate secrets with an overlap window
The package's secret rotation story is unverified. The safe pattern regardless of implementation: accept both old and new secret on the consumer for a window, swap the secret in Nova, wait out the longest in-flight retry backoff, then drop the old secret. Rotating without overlap drops every delivery already queued with the old signature.
Restrict outbound URLs — this is an SSRF surface
Anyone with Nova access to create a webhook can make your server issue HTTP requests to arbitrary internal addresses (169.254.169.254, localhost, internal admin panels on 10.x). If your Nova users are all trusted admins, note it and move on. If not, add an allowlist or push endpoint creation behind an approval workflow, and apply egress network policy as a second layer.
Gate the Nova resource with a policy
Webhook endpoints and delivery logs contain signing secrets and full model payloads. Default-deny: write a Nova policy and restrict to the smallest group that needs it. A delivery log showing a User payload with hashed passwords or an Order with customer PII is a data-exposure incident, not a convenience.
Watch delivery-log growth
Logs accumulate per attempt, including retries. A flapping consumer endpoint generates retries whose log rows outnumber successful deliveries. Confirm whether there is pruning/retention support before launch; if not, schedule a pruning job against the log table yourself from day one. Otherwise this table quietly becomes your fastest-growing storage cost.
Queue worker concurrency is your throughput ceiling
Per-endpoint HTTP timeout × worker count determines maximum throughput. Size for peak event volume, not average. A consumer that gets slow (not down — just slow) will hold workers and back up unrelated jobs in the same queue. Isolate webhook delivery onto its own queue if the package's config allows it — that one setting prevents a flapping external endpoint from delaying your application's own background work.
Keep the integration thin
Given the low star count, unknown maintainer cadence, and a delivery path now spanning two packages from the same vendor organization, keep business logic out of the package's extension points. Payload transformation should be thin and declarative; domain logic stays in your actions. If support degrades, replacing the dispatcher becomes a config change rather than a rewrite.
Don't fight an existing dispatcher
If you already run spatie/laravel-webhook-server or an in-house implementation, evaluate whether this adds real value — the Nova admin UI and delivery logs are the differentiator. Delivery itself, signing, and retries are commodities. Adding a second dispatcher to the same app to get the UI is a bad trade; adding it to replace the first one is worth a dual-run period before cutover.
Clear extension points after every upgrade
If you've published config or added payload transformers, re-check them after any opscale-co/actions bump. Extensions are the part that breaks silently — the package works, your transform throws, and you find out when a consumer complains about missing fields.
How can I help you explore Laravel packages today?