ohdearapp/laravel-ohdear-webhooks
Handle Oh Dear webhooks in your Laravel app with automatic signature verification and configurable jobs or events dispatched for each webhook type. Just install, publish the config, set your signing secret, and register the route.
Architecture fit
The package is a thin inbound adapter, not a platform component. Its entire surface area is four things:
config/ohdear-webhooks.php.Route::ohDearWebhooks('...') — binding a POST endpoint to a package controller.signing_secret, compared with hash_equals (constant-time; fixed in 1.0.1).uptimeCheckFailed, uptimeCheckRecovered, …) to either a queued job from the jobs config array, or a Laravel event named ohdear-webhooks::<type>.Payload access is deliberately loose: OhDearWebhookCall exposes payload (array), type(), monitor(), run(), dateTime(). The package explicitly refuses to own what happens after validation — a correct and healthy scope boundary, and the natural fault line between "package responsibility" and "our responsibility" in any design review.
For a standard Laravel app, this is the right shape: no new middleware stack, no persistence layer, no infrastructure. It is a ~200-line convenience wrapper. A TPM should frame the decision as buy-vs-build for a commodity, not as an architectural commitment.
Integration feasibility
Low effort, high predictability. Composer install, publish config, register the route, exclude the route from VerifyCsrfToken, implement one or more ShouldQueue jobs. A competent Laravel engineer completes a first working webhook in under a day, including the queue wiring.
The genuine work is downstream and sits entirely on our side: idempotency, alert routing, escalation policy, and observability. The package gives us nothing there, by design.
Technical risk
| Risk | Severity | Notes |
|---|---|---|
| Stringly-typed vendor contract | Medium | Event names in jobs are free-text keys matched against Oh Dear's catalog. PR #13 ("Update config to match actual types") is direct evidence of drift having already occurred. A typo or a vendor rename produces silent non-dispatch. |
| No replay/dedupe protection | Medium | Signature verification proves origin, not freshness. Duplicate deliveries on non-200 responses must be deduped downstream using type() + monitor() + dateTime(). |
| Opaque major version | Medium | 2.0.0 ships with a changelog entry reading only "v2 by @freekmurze." Breaking-change surface must be read from the diff, not the notes. |
| Unversioned payload schema | Medium | monitor() / run() return raw arrays shaped by the vendor. Attribute renames are a silent runtime break. |
| CSRF exclusion footgun | Low–Medium | Manual $except entry is required; a route rename that misses this array fails closed (419) and looks like a signing failure. |
| Adoption/bus factor | Low | 23 stars, 0 dependents — but maintained by the Oh Dear team itself (Freek Van der Herten / Mattias Geniar), i.e. the vendor dogfoods it. Small surface means a fork is a day's work if it ever stalls. |
| Silent total outage | High impact, low likelihood | A rotated or mistyped secret causes every webhook to throw WebhookFailed. Fail-closed is correct, but invisible without a canary. |
Key questions
OH_DEAR_SIGNING_SECRET rotation runbook?Stack fit
Laravel-native, conventional, no opinions imposed beyond the route macro. Changelog evidences a maintained compatibility ladder: Laravel 5.6 through 11 tracked release-by-release, PHP 8 permitted from 1.4.1. Confirm the 2.x support matrix against composer.json rather than inferring it — 2.0.0 postdates the L11 support in 1.4.4 and likely targets newer framework/PHP baselines.
Migration path
Two distinct migrations, and they should not be conflated:
^1.4 until the diff is understood, then bump in a branch with the full webhook test suite run. Given the changelog gives no migration guidance, budget a half-day of engineering due diligence before the bump, not after.Compatibility
Three concrete coupling points: the CSRF $except array; queue configuration (the package docs strongly recommend queueable jobs/listeners — treat this as mandatory, not advisory); and Oh Dear's own webhook settings screen, which must point at our route and hold the same secret we configure.
Sequencing
.env in the repo.ShouldQueue, each mapping to an internal domain event rather than raw payload handling.type + monitor id + dateTime().jobs key maps to an existing, queueable class.Step 4 matters most strategically: wrapping the package's dispatch in our own domain events keeps the vendor package at arm's length and makes it replaceable in a day.
Maintenance
Low code surface, moderate coupling surface. Ongoing work is not patching this package — it is tracking two moving targets in parallel: package releases (a handful per year, currently paced to Laravel majors) and Oh Dear's webhook event catalog (which we have already seen change once). Realistic envelope: a few hours per quarter, plus a re-verification whenever we upgrade Laravel.
Support
MIT, community-supported, no SLA. Escalation path is the repo's issue tracker; security disclosures go to [email protected]. The mitigating factor is that the maintainers are the vendor — this package exists because Oh Dear needs it to work. Combined with the tiny surface, fork-and-own is a credible fallback, which materially lowers the vendor risk relative to the 23-star signal.
Scaling
Not a throughput concern. Volume is bounded by monitors × check frequency × incident rate — orders of magnitude below anything a single web endpoint worries about. The scaling questions that matter are downstream: queue worker concurrency, and whether a burst of failures (a regional outage triggering hundreds of simultaneous uptimeCheckFailed events) would stampede our alerting or ticketing system. Plan for burst fan-out and per-monitor alert throttling.
Failure modes
| Mode | Symptom | Detection | Mitigation |
|---|---|---|---|
| Wrong/rotated secret | All requests throw WebhookFailed, 100% loss |
None by default — silent | Synthetic canary webhook + alert on zero-inbound-for-N-hours |
| Unmapped event type | Event arrives, nothing dispatched | None | Config-validation test; log-and-alert on unmapped types |
| Missing job class | jobClassDoesNotExist exception |
500s visible in logs | Class-existence assertion in CI |
| Queue backlog | Webhook returns 200, work never runs | Queue depth metric | Separate queue with its own autoscaling and latency SLO |
| Duplicate delivery | Double-paging, double-ticket | Downstream only | Idempotency key on type + monitor + dateTime() |
| Payload schema drift | Nulls or missing keys in monitor()/run() |
Silent until a code path reads the field | Defensive accessors; pin behavior with fixture-based tests |
| CSRF exclusion removed | 419 on every request | 419 spike in logs | Route-level test asserting the exclusion holds |
The two failure modes to fund explicitly are the silent ones: bad secret and unmapped event type. Everything else surfaces loudly.
Ramp-up
Genuinely low. A Laravel developer is productive in half a day from the README alone. The friction is documentation quality, not difficulty: the Usage section references (events) as a relative link in two places, which is broken prose rather than a working URL — so the event catalog must be sourced from Oh Dear's live docs, not from this repo. Budget an hour for someone to build the authoritative event-type list before any code is written; that artifact, not the package, is what future maintainers will actually lean on.
How can I help you explore Laravel packages today?