Guide to Oh Dear Webhooks: Features and Best Practices
Oh Dear watches websites and applications from the outside: uptime over HTTP, TCP or ping, certificate health, broken links, mixed content, DNS records, scheduled tasks, application health, sitemaps and more. When one of those checks fails or recovers, Oh Dear can notify your team by email or chat, or call a webhook so your incident tooling, status page or own code can respond automatically.
This guide explains how Oh Dear webhooks are configured per team, the event names that actually appear on the wire, how to verify OhDear-Signature, and why your endpoint must answer with exactly 200.
What are Oh Dear webhooks?
Oh Dear webhooks are part of a team's notification settings. You add one or more webhook URLs, and Oh Dear posts every notification event to them as JSON. There's no event selection on the webhook itself: what you receive follows the team's notification settings.
Each team has a single signing secret, displayed in the team settings as Web hook signing secret. Every webhook is signed with it. For endpoints that also require their own credentials, a webhook destination can carry up to 5 custom headers, which Oh Dear sends only to URLs with a valid TLS certificate.
Oh Dear webhook features
| Feature | Details |
|---|---|
| Signature | hex HMAC-SHA256 |
| Signature header | ohdear-signature |
| Body | JSON |
| Event routing | Body field type |
From the Oh Dear webhook documentation:
- Only an HTTP
200is success. Any other status, including a301or302redirect, is a failure. - Each event is tried up to 3 times, with a 3-second timeout. After three failures, that event isn't sent again.
- A URL that fails for 48 hours without one success is paused, and the team is emailed.
- Each destination keeps a 5-day webhook log with a Resend button.
Events and payloads
| Event routing | Body field type |
Oh Dear documents 37 event types. Uptime events come in http, tcp and ping variants of UptimeCheckFailedNotification and UptimeCheckRecoveredNotification. Most other checks have paired events, such as dnsIssuesFoundNotification and dnsIssuesFixedNotification, and there are one-off events such as certificateExpiresSoonNotification, cronNotExecutedOnTimeNotification and the aiCheck* events.
The docs' contents list uses class-style names like UptimeCheckFailed and CronFailed. Those aren't what arrives in type, so match the full ...Notification strings. One example payload on the docs page also shows a misspelt sentCronNotExutedOnTimeNotification, while the documented name is cronNotExecutedOnTimeNotification; log both until you've seen a real delivery.
Every body includes uuid, type, date_time and result_url, which links to the exact run or report in Oh Dear. Event data follows, usually a monitor object with its checks. Checks have their own type keys such as "uptime", so route only on the top-level type. This is the monitorAddedNotification sample from the webhook samples library, which omits the generic fields:
{
"type": "monitorAddedNotification",
"date_time": "20191001113659",
"monitor": {
"id": 1,
"url": "https://yoursite.tld",
"uses_https": true,
"sort_url": "yoursite.tld",
"label": "yoursite.tld",
"team_id": 1,
"latest_run_date": null,
"summarized_check_result": "pending",
"created_at": "2019-10-01 09:36:59",
"updated_at": "2019-10-01 09:36:59",
"checks": [
{
"id": 39675,
"type": "uptime",
"enabled": true,
"monitor_id": 1,
"latest_run_result": null,
"latest_run_ended_at": null,
"latest_run_id": null
},
{
"id": 39676,
"type": "broken_links",
"enabled": true,
"monitor_id": 1,
"latest_run_result": null,
"latest_run_ended_at": null,
"latest_run_id": null
},
{
"id": 39677,
"type": "mixed_content",
"enabled": true,
"monitor_id": 1,
"latest_run_result": null,
"latest_run_ended_at": null,
"latest_run_id": null
},
{
"id": 39678,
"type": "certificate_health",
"enabled": true,
"monitor_id": 1,
"latest_run_result": null,
"latest_run_ended_at": null,
"latest_run_id": null
}
]
}
}
date_time is a compact YYYYMMDDHHMMSS string rather than ISO 8601. The check IDs let you fetch each check's latest result from the Oh Dear API once it has run.
Setting up Oh Dear webhooks
- In Oh Dear, open the team settings and copy the Web hook signing secret. Store it as
OHDEAR_SIGNING_SECRET, used as-is. - Go to the team's notification settings, scroll to the webhooks section and add your URL, for example
https://example.com/webhooks/oh-dear. - Use the exact final URL. A redirect, such as
httptohttpsor a missing trailing slash, counts as a failure. - If your endpoint needs its own credentials, open the webhook's Advanced settings and add custom headers.
With Hookdeck, create an Oh Dear source with the team's secret as its verification secret, and add the source URL in step 2. For local testing:
npx hookdeck-cli listen 3000 oh-dear --path /webhooks/oh-dear
Adding a monitor triggers monitorAddedNotification, which makes a good first test.
Securing Oh Dear webhooks
OhDear-Signature holds a hex HMAC-SHA256 of the raw body, keyed with the team's signing secret. Compute it over the bytes received and compare in constant time. Oh Dear's Laravel package does this for you on PHP. Verification is optional on Oh Dear's side, but your endpoint should always do it.
Node.js verification
const crypto = require('node:crypto');
function verifySignature(rawBody, headers, secret) {
if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
const signature = headers['ohdear-signature'];
if (typeof signature !== 'string') return false;
const key = Buffer.from(secret, 'utf8');
const message = rawBody;
const candidates = [signature];
const expected = crypto.createHmac('sha256', key).update(message).digest();
return candidates.some((candidate) => {
if (!/^[0-9a-fA-F]{64}$/.test(candidate)) return false;
const received = Buffer.from(candidate, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
The handler returns exactly 200 once the event is stored, keyed by uuid:
const express = require('express');
const app = express();
app.post('/webhooks/oh-dear', express.raw({ type: '*/*' }), async (req, res) => {
const secret = process.env.OHDEAR_SIGNING_SECRET;
if (!secret) return res.sendStatus(500);
if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);
let event;
try {
event = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
if (!event || typeof event !== 'object' || Array.isArray(event)) return res.sendStatus(400);
try {
await recordOhDearEvent(event.uuid, {
type: event.type,
monitorUrl: event.monitor?.url ?? null,
resultUrl: event.result_url ?? null,
body: event,
});
} catch {
return res.sendStatus(503);
}
res.status(200).end();
});
recordOhDearEvent ignores a uuid it already holds, since both automatic retries and manual resends keep the original value. A worker can then open or resolve an incident, linking to result_url so the on-call engineer lands on the right report.
No timestamp is signed. Hookdeck's Oh Dear source verifies the same HMAC, replies 200, which is the only status Oh Dear accepts, and records a mismatch as failed verification.
Make Oh Dear webhooks production-ready. Hookdeck Event Gateway answers Oh Dear with a
200inside 3 seconds every time, so alerts aren't lost to slow handlers and the URL is never paused, then retries your incident tooling until it accepts each event.
Oh Dear webhook limitations and pain points
Three tries within 3 seconds each
The Problem: An alert sent while your endpoint is slow or briefly down is gone after three failures.
Why It Happens: Oh Dear treats a timeout of 3 seconds or more, or any status other than 200, as a failure, and gives up on that event after three attempts.
Workarounds:
- Return
200immediately and do the work afterwards. - Check the 5-day webhook log after an incident and use Resend for anything missed.
How Hookdeck Can Help: Hookdeck accepts each alert on the first attempt and retries your service on a schedule you set, well beyond three tries.
Only 200 counts
The Problem: A 201, 202, 204 or redirect is treated as a failure.
Why It Happens: Oh Dear's success rule is a literal HTTP 200.
Workarounds:
- Return
200explicitly, even from frameworks that default to204. - Register the final URL so no redirect occurs.
How Hookdeck Can Help: Hookdeck always answers Oh Dear with 200, whatever your own service returns.
Failing URLs are paused
The Problem: After two days of failures, Oh Dear stops sending to the URL.
Why It Happens: A webhook URL that fails for 48 hours without a single success is paused until someone re-enables it.
Workarounds:
- Make sure the team's email notifications reach someone.
- Re-enable the webhook in notification settings once the endpoint works.
How Hookdeck Can Help: Hookdeck keeps the URL healthy from Oh Dear's point of view and holds events for your service until it recovers.
Partial and inconsistent examples
The Problem: Most payload examples are abridged, and one shows a misspelt event name.
Why It Happens: Only the monitorAddedNotification example is complete. Others elide the monitor block or arrays, and the contents list uses class names rather than wire values.
Workarounds:
- Route on the documented
...Notificationnames and log anything unrecognised. - Capture real deliveries for each check type you monitor.
How Hookdeck Can Help: Hookdeck stores each delivery in full, giving you real examples for every event type your team triggers.
Best practices
Deduplicate on uuid
The uuid stays the same on retries and resends. Store it uniquely, following the webhook idempotency guide.
Pair failed and recovered events
Most failure events have a matching recovered or fixed event. Use the monitor ID and check type to close incidents opened by the failure.
Keep handlers out of the 3-second path
Creating an incident or posting to chat can be slow. Queue it, as explained in why to process webhooks asynchronously.
Pass on result_url
Include result_url in any alert you forward so the person who picks it up can open the report directly.
Conclusion
Oh Dear webhooks deliver every notification your team's monitors raise, signed with a hex HMAC-SHA256 of the body in OhDear-Signature. Verify the raw bytes, answer with exactly 200 inside 3 seconds, and deduplicate on uuid, because each event gets only three tries and a URL that keeps failing is paused.
Hookdeck Event Gateway takes Oh Dear's strict delivery rules off your hands while your alerting pipeline does its work.
Get started with Hookdeck for free and handle Oh Dear webhooks reliably in minutes.