Skip to content

Heartbeat Monitors

A heartbeat monitor watches a job that runs somewhere else: a crontab line, a GitHub Actions workflow, a Vercel cron route, a database job. Chronos never runs the job. The job calls the monitor’s ping URL when it finishes, and Chronos alerts you when a ping reports a failure or doesn’t arrive in time.

Terminal window
curl -fsS -m 10 --retry 5 -o /dev/null https://ping.chronos.sh/k3Jx9Qm2TnV7bRw4YcZ8dHfL

Every example on these pages uses that placeholder ping URL. Replace it with your monitor’s ping_url, which the dashboard shows when you create the monitor. The token at the end of the URL is the only credential, so keep the URL out of public repositories and logs.

  1. Create a monitor in the dashboard, or with POST /v1/monitors. Give it the cadence your job runs on.
  2. Copy the ping_url.
  3. Add a ping to the end of your job. Pick your platform:
  1. Run the job once. The monitor stays new and sends no alert until its first success or fail ping arrives.
Path Means
/<token> The job succeeded.
/<token>/fail The job failed.
/<token>/<exit-code> The job exited with this code, from 0 to 255. 0 is a success and any other code is a failure.
/<token>/start The job started. Optional.

A start ping never changes the monitor’s status or deadline. It gives the next success or fail ping a duration, so the ping history shows how long each run took.

GET, HEAD, and POST all work. Chronos stores up to 10 KB of a POST body with the ping. See the Ping API for responses and rate limits.

In application code, ping at the end of the job and on the failure path:

const pingUrl = process.env.CHRONOS_PING_URL;
async function ping(path, body) {
try {
const res = await fetch(pingUrl + path, { method: 'POST', body, signal: AbortSignal.timeout(10_000) });
if (!res.ok) console.error('chronos ping failed', res.status, await res.text());
} catch (err) {
console.error('chronos ping failed', err);
}
}
await ping('/start');
try {
await runJob();
} catch (err) {
await ping('/fail', String(err));
throw err;
}
await ping('');

The ping helper logs a lost ping and returns, so a network error or a non-200 response, such as 404 for a rotated URL, never fails the job. On those errors the log line leaves out the URL, because the token in the URL is the monitor’s credential. If the success ping is lost, the monitor goes down after the grace period.

A monitor has a cadence, either a cron expression or an interval, and a grace period.

  • Interval: the next ping is due one interval after the last success or fail ping.
  • Cron: the next ping is due at the next cron occurrence, in the monitor’s timezone (your account’s timezone unless you set one). A ping that arrives a little early counts for the upcoming occurrence. Early means within the smaller of the grace period and half the gap between occurrences.
  • Grace period: how long Chronos waits after a ping is due before it alerts. The grace period defaults to 5 minutes and can be set from 60 seconds to 7 days. Set it to cover how long the job runs plus any scheduling delay on its platform.

The cadence can’t be more frequent than every 60 seconds, on every plan. An interval can be up to 365 days, and a cron expression must fire at least twice in the next 2 years.

The API returns the current deadlines on every monitor: next_ping_due_at is when the next success or fail ping is expected, and alert_at is that time plus the grace period.

Status Means
new Created or resumed, and no success or fail ping since. Never alerts.
up The last ping was a success, and the next one isn’t due yet.
late up, but next_ping_due_at has passed. Still inside the grace period, so no alert yet.
down The last ping was a failure, or no ping arrived by alert_at.
paused Sends no alerts and has no deadline.

Chronos checks deadlines once a minute, so a missed-ping alert can arrive up to a minute after alert_at.

A monitor sends one down alert when it goes down, and one up alert when it recovers. It goes down in two ways:

  • A fail ping arrives, from /fail or a non-zero exit code. The alert includes the exit code and the first 500 characters of the ping’s body.
  • No success or fail ping arrives by alert_at. The alert includes when the ping was due, the last ping, and the grace period.

While a monitor is down, more fail pings or lost pings send no further alerts. The next success ping brings it back up and sends the up alert.

Monitor alerts go to every channel enabled in the dashboard’s alert settings: email, Slack, and a signed webhook. The job alert triggers and the missed-run threshold don’t apply to monitors. No setting turns monitor alerts off. To stop alerts from one monitor, pause it.

  • Pause stops alerts and clears the deadlines. Pings to a paused monitor still return 200 and update last_ping, but they aren’t kept in the ping history and change nothing else. Pausing a down monitor sends no up alert.
  • Resume puts the monitor back to new, so the next success or fail ping sets its deadline. To cover planned downtime, pause the monitor and resume it afterwards. The skipped runs then send no alert.
  • Delete removes the monitor and its ping history. The ping URL stops working.

If a ping URL leaks, rotate it from the dashboard or with POST /v1/monitors/:id/ping-url/rotate. The old URL returns 404 from that moment, so update every job that uses it. A job still on the old URL stops pinging, and the monitor goes down after the grace period.

Each plan caps active monitors. See Pricing & Limits.