Native fetch (Node 18+) — works with node-cron, Agenda, BullMQ repeatable jobs, or a bare setInterval.
node-cron, Agenda, and similar in-process schedulers all share one structural weakness: the schedule lives inside your application process. If that process crashes, gets OOM-killed, or a process manager like PM2 restarts it in a crash loop, the scheduled task simply stops firing — and there is no separate watchdog telling you, because the thing that would normally report the problem is the thing that died. A second failure mode is quieter: an async callback inside cron.schedule() that throws becomes an unhandled promise rejection. Depending on Node version and flags, that either crashes the whole process or gets logged to stderr and swallowed — in a container with no log alerting, that's the same as never happening. Timezone bugs are the third common cause: node-cron's timezone option is easy to omit, so a job scheduled for "4am" silently runs at the container's UTC 4am instead of the business's local 4am, or shifts an hour after a DST change nobody accounted for.
Ping at both the start and the end of the scheduled callback. .catch(() => {}) on the ping calls themselves so a CronCanary outage never becomes your job's outage:
Reuse the same pattern across every scheduled function instead of duplicating the try/catch:
Both frameworks give you a job handler function — apply the same monitored() wrapper around the handler body (Agenda's agenda.define("name", monitored(URL, handler)), or inline inside a BullMQ Worker processor). Set the check's schedule to match the cron expression or repeat interval you registered, in the queue's configured timezone.
Because the schedule lives inside the app, add a global process.on("unhandledRejection", ...) and process.on("uncaughtException", ...) handler that fires the /fail ping before the process exits or PM2 restarts it — otherwise a crash between two runs can go unreported until the next missed ping trips the grace period on its own.
Every check has a public SVG badge that shows its live status (updates within ~1 minute). Paste this into any README — it doubles as a heartbeat anyone on the team can see:
Copy the exact markdown from your check's detail page. Add ?label=your-text to customize the left label.
Ready to wire this up? Create a free check — 20 checks, all alert channels, no card.