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.
Start by installing and wiring the package into the app’s existing webhook surface area:
composer require ohdearapp/laravel-ohdear-webhooks
php artisan vendor:publish --provider="OhDear\LaravelWebhooks\OhDearWebhooksServiceProvider" --tag="config"
Then:
OH_DEAR_SIGNING_SECRET in .env using the secret from the Oh Dear team notification settings.Route::ohDearWebhooks('ohdear-webhook');
App\Http\Middleware\VerifyCsrfToken::$except, since Oh Dear cannot send a CSRF token.config/ohdear-webhooks.php:
'jobs' => [
'uptimeCheckFailed' => \App\Jobs\OhDear\HandleFailedUptimeCheck::class,
],
The first place to look after publishing is config/ohdear-webhooks.php, followed by the package’s OhDearWebhookCall class and the Oh Dear webhook event documentation. The first useful implementation is usually an uptimeCheckFailed job that sends a notification or opens an incident, paired with an uptimeCheckRecovered job that closes it.
One queued job per event type. This is the primary day-to-day pattern. Keep the job focused: read $webhookCall->monitor(), $webhookCall->run(), and $webhookCall->type(), then perform a single side effect such as Slack notification, email, SMS, PagerDuty-style escalation, or incident creation. Jobs should implement ShouldQueue so the webhook endpoint returns quickly.
class HandleFailedUptimeCheck implements ShouldQueue
{
public function __construct(public OhDearWebhookCall $webhookCall) {}
public function handle(): void
{
$monitor = $this->webhookCall->monitor();
$run = $this->webhookCall->run();
// open incident keyed by monitor id, notify on-call, etc.
}
}
Event listeners for cross-cutting behavior. Every valid webhook can also be consumed as a Laravel event named ohdear-webhooks::<event-type>. Use listeners for audit logging, metrics, or a generic “store every webhook payload” listener rather than duplicating that logic in every job.
protected $listen = [
'ohdear-webhooks::uptimeCheckFailed' => [
\App\Listeners\StoreOhDearWebhook::class,
],
];
For broad capture, register a wildcard listener in EventServiceProvider::boot():
Event::listen('ohdear-webhooks::*', function (OhDearWebhookCall $call) {
// append to audit log, increment metrics, etc.
});
Correlate failure and recovery events. Treat monitor() as the join key. On uptimeCheckFailed, open or update an incident using the monitor id/url. On uptimeCheckRecovered, close the matching incident. This is the simplest way to build an on-call workflow without building custom webhook parsing.
Use jobs for side effects, events for observation. A practical split is: jobs own “do the thing” behavior (notify, page, create ticket), while event listeners own “record that it happened” behavior (audit trail, analytics, replay storage). This keeps the config mapping small and makes failures easier to reason about.
Test locally by simulating signed requests. Oh Dear cannot reach localhost, so use a tunnel or generate a signed POST in a feature test. Inspect the package’s signature validator to replicate the exact header and signing input; then assert that the configured job is dispatched and that invalid signatures throw OhDear\LaravelWebhooks\WebhookFailed.
Deploy with config caching in mind. After changing the jobs mapping, run php artisan config:cache as part of deployment. A stale config cache is a common reason a newly mapped job never runs.
VerifyCsrfToken::$except, Oh Dear requests will fail before signature verification.200. The package validates and dispatches, then responds. If your queued job later fails, Oh Dear will not retry the webhook. Rely on Laravel queue retries and failed-job monitoring; for critical flows, persist the webhook before dispatch or add explicit retry/alerting around the job.dateTime(), or store a processed-event key before performing side effects.WebhookFailed. Check the signing secret, the environment actually loaded by the app, and whether config is cached. A mismatched secret produces a failed request, not a silent skip.uptimeCheckFailed. A typo means no job is dispatched; verify names against the Oh Dear webhook event docs and the package’s published config comments.$webhookCall->payload and the typed accessors yourself.monitor() and run() return arrays whose keys can vary by event type. Use null coalescing and avoid assuming every event has a full run payload.dateTime() is a string. Parse it explicitly rather than relying on implicit string-to-date conversion, and avoid assuming a timezone beyond what Oh Dear sends..env, rotate it in both Oh Dear and the app when needed, and use HTTPS.signing_secret. If you need per-team verification, you will need separate endpoints/apps or custom handling outside the package.How can I help you explore Laravel packages today?