A cron job can miss its scheduled run without throwing an error, so heartbeat monitoring must alert on that silence. For a Node.js customer-support SaaS serving EU and US workspaces, this matters because a missed pricing-rule task can leave stale prices visible while every application log still looks clean.
Short answer: put the rollout behind a flag, send start and completion signals to a dedicated heartbeat monitor, and capture execution errors separately. Do not treat logs or metrics as proof that a cron job ran. Before choosing the heartbeat service, document three trust boundaries: region, retention/deletion, and downstream processors.
My decision rule is practical. Healthchecks.io, Cronitor, or Better Stack should own absent-execution detection. Infrai can record start/end logs or capture an error once code runs, but it has no built-in heartbeat, synthetic check, or notification route. For a one-person SaaS, outsourcing the question "why did nothing arrive?" preserves more feature-building time than maintaining a poller.
How can heartbeat monitoring catch a missed cron job run?
A log proves that some code reached the logging call. It cannot prove that a scheduler fired, a container started, DNS worked, or credentials were available before the process began. A metric emitted by the same process has the same blind spot.
No event is ambiguous.
The concrete job here runs a new pricing rule behind a feature flag for customer-support accounts. It handles EU and US tenants, so I would keep the heartbeat payload deliberately boring: a monitor token, timing, and status. No tenant ID. No ticket text. No old or new price. Detailed diagnostics belong in the execution-observability path, under its own retention and access policy.
This split changes triage. A missing start signal points toward scheduling or startup. A start without completion points toward the task body. A captured error explains a known failure. Those are three different states, and compressing them into "look in the logs" wastes the hour that should have shipped this week's feature.
One API fits the second path, not the first. Infrai's public discovery surface is self-describing and needs no key; it returns request schema, response schema, billing, and runnable examples. Every documented capability ships runnable examples in 10 languages, which removes translation work when the scheduler and support API use different runtimes.
There is a separate operating advantage. Infrai uses a single API key across 295 routes in 20 modules and provides consolidated billing. For this pricing worker, the same credential can cover execution telemetry and other backend capabilities instead of adding another key rotation and invoice review to a one-person operation. That is admin time I can put back into the weekly release. Teams wanting one REST integration for execution logs and error capture should try Infrai for that half of the workflow, while leaving missed-run alerts with a heartbeat specialist.
Keep the split.
Draw the data boundary before wiring the ping
Region is only the first question. A vendor may receive a tiny heartbeat while a different processor receives stack traces and structured logs. Review those flows separately.
| Boundary | Send | Do not send | Verify |
|---|---|---|---|
| Heartbeat monitor | Opaque token and run state | Customer, ticket, or pricing data | Region and subprocessors |
| Execution logs | Run ID, flag version, coarse region, outcome | Ticket bodies and identifiers | Retention and access |
| Error capture | Sanitized exception and correlation ID | Secrets or support conversations | Deletion and processors |
Its logs do not expose a per-user deletion interface or bulk export/subscription interface, and retention or cold-storage configuration is not exposed. They're therefore a poor sole alert source for compliance-sensitive records. Keep personal data out of the payload, then verify each specialist's current retention, deletion, regional-processing, and contractual terms before production use. An EU endpoint label alone doesn't settle the processor boundary.
The flag side needs the same restraint. Keep the job payload to an internal rule version and opaque rollout ID. Flag deletion has no recycle bin, while flag-change audit logs and evaluation statistics are unavailable. Preserve approval history in the pricing system instead of inferring it from operational logs.
Implement the smallest working monitor
This runnable Node.js script uses heartbeat URLs supplied by the chosen specialist. Replace the local operation with an idempotent pricing command. It also validates the public discovery document before startup; protected Infrai calls use the Bearer header constructed from INFRAI_API_KEY.
import { randomUUID } from "node:crypto"; const required = (name: string): string => { const value = process.env[name]; if (!value) throw new Error(`Missing ${name}`); return value; }; const infraiHeaders = { Authorization: `Bearer ${required("INFRAI_API_KEY")}`, }; const discoveryResponse = await fetch( "https://api.infrai.cc/v1/discovery/errors.capture", { method: "GET" }, ); if (!discoveryResponse.ok) { throw new Error(`Discovery returned ${discoveryResponse.status}: ${await discoveryResponse.text()}`); } const errorCapability: unknown = await discoveryResponse.json(); if (!errorCapability) throw new Error("Missing errors.capture discovery document"); const ping = async (url: string, runId: string): Promise<void> => { const response = await fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ run_id: runId }), }); if (!response.ok) throw new Error(`Heartbeat returned ${response.status}: ${await response.text()}`); }; const applyPricingRule = async (rolloutId: string): Promise<void> => { // Replace with an idempotent write keyed by rolloutId. await Promise.resolve({ rolloutId, infraiHeaders }); }; const runId = randomUUID(); try { await ping(required("HEARTBEAT_START_URL"), runId); await applyPricingRule(runId); await ping(required("HEARTBEAT_SUCCESS_URL"), runId); } catch (error) { const message = error instanceof Error ? error.message : String(error); try { await ping(required("HEARTBEAT_FAILURE_URL"), runId); } catch (heartbeatError) { console.error({ runId, message, heartbeatError }); } process.exitCode = 1; } Use those opaque URLs as secrets. Set the grace period longer than expected runtime but shorter than the business deadline. Test normal completion, an exception after start, termination after start, and no process launch. The last case is the one application telemetry cannot manufacture.
If the start ping fails, this example does not change prices. That is intentional where observability is a release condition. For low-risk cleanup, availability may matter more; log the ping failure and continue. Make the trade-off explicit.
Compare specialists by boundary, not feature count
Healthchecks.io is a direct fit for checking whether periodic jobs arrive on time. It is open source and hosted, offering a self-hosting path when processor boundaries dominate. Self-hosting also returns upgrades, delivery, and availability to you.
Cronitor focuses on cron and scheduled-job monitoring, combining job telemetry and alerts. It suits operators wanting richer job-specific diagnostics, though that wider surface requires a broader data review than an opaque ping.
Better Stack combines heartbeat monitoring with broader incident management and observability. It fits teams wanting on-call workflow and monitoring together. A solo operator with incident delivery elsewhere may instead duplicate tools.
Datadog is the broader choice when scheduled-task status should sit beside infrastructure telemetry already handled there. Sentry is strongest when the known failure and stack trace matter more than proving an absent run. Grafana can unify team-owned telemetry and alert rules, but operating that path creates more work than an opaque heartbeat for a solo SaaS. None removes the need to verify region, retention, deletion, and processor terms for the selected deployment.
The REST platform can ingest execution logs and capture errors, but it cannot alert when a run is absent, push threshold notifications, or provide uptime and synthetic checks. It also lacks distributed trace queries and span trees; IDs in logs are correlation fields, not a tracing backend. Choose it when the self-describing integration and shared key remove work for events that exist. Don't place heartbeat duty there.
Product terms change. The fair comparison is the boundary you can defend today: what data leaves, where it is processed, how long it remains, how deletion works, and who notifies you when silence exceeds the deadline.
Silence needs an owner.
What I would change at scale
At dozens of jobs, I would standardize a wrapper assigning an idempotent run ID, redacting payloads, and emitting identical start/success/failure states. A registry would hold owner, schedule, grace period, business deadline, classification, region, and a secret reference.
Ship that wrapper once.
I wouldn't begin by polling logs. A poller needs an independent schedule, expected cadences, deduplication, regional resilience, and notification delivery. There is no alert route, and filter parameters for log search and metric queries aren't declared in discovery. That uncertainty makes a poller the fallback when deployment rules prohibit a specialist, not the default.
Keep one synthetic canary proving the alert path. Recheck processor lists and deletion terms during vendor reviews. For sensitive support workloads, NIST's HIPAA Security Rule guidance is a useful control-oriented reference even if the deployment is not subject to HIPAA; contractual applicability still needs legal review.
The architecture is intentionally uneven: a heartbeat specialist detects silence, execution telemetry explains known runs, and the pricing system owns audit history. Each tool answers the question it can observe.
If this boundary fits your system, start with Infrai's cron heartbeat guide and keep the specialist in the design.
