Heartbeat

Did the scheduled job actually run? The monitor that waits to be told, with copy-paste examples.

Every other monitor goes out and looks. A heartbeat sits and waits: your job calls a URL when it finishes, and if the call does not arrive on time, the monitor goes down.

Use it when

Anything runs on a schedule and failing silently would be bad: backups, cron jobs, ETL runs, certificate renewal, nightly reports, queue drains.

This is the monitor that catches the backup which stopped working in March and was noticed in September.

It is also the way to monitor something we cannot reach — a job inside a private network can call out to us even though we can never call in.

What to enter

FieldNotes
ScheduleEvery N minutes/hours/days, or a cron expression.
TimezoneFor cron schedules, so "3am" means your 3am.
Grace periodHow late is acceptable before it counts as missed.

Set the grace period to cover how long the job legitimately takes to vary. A backup that normally finishes at 02:10 but occasionally at 02:40 needs at least 30 minutes of grace, or you will be paged for a slow Tuesday.

The ping URL

Each heartbeat has its own URL, shown on the monitor's page:

https://.../h/<token>

Call it when the job succeeds. GET or POST, either is fine.

URLMeaning
/h/<token>Success. The job finished.
/h/<token>/startThe job started. Optional; it is what gives you a duration.
/h/<token>/failThe job failed. Goes down immediately.
/h/<token>/<exit code>0 is success, anything from 1 to 255 is a failure.

The exit-code form is what makes the shell idiom work, because $? is already the number you want.

Exactly when it's DOWN

  • No ping arrives within the schedule plus the grace period.
  • A /fail ping arrives.
  • A ping with a non-zero exit code arrives.

The failure threshold is fixed at 1: one missed run is the event you wanted to hear about. There are no regions, and no latency alerts.

Examples

crontab — report the real exit status:

0 2 * * * /usr/local/bin/backup.sh; curl -fsS -m 10 https://.../h/<token>/$?

bash, with a start marker so you get a duration:

curl -fsS -m 10 https://.../h/<token>/start
./run-import.sh
curl -fsS -m 10 "https://.../h/<token>/$?"

GitHub Actions:

- name: Report to UptimeCraft
  if: always()
  run: curl -fsS -m 10 "https://.../h/${{ secrets.HEARTBEAT_TOKEN }}/${{ job.status == 'success' && '0' || '1' }}"

Python:

import urllib.request
urllib.request.urlopen("https://.../h/<token>", timeout=10)

PowerShell:

Invoke-RestMethod -Uri "https://.../h/<token>" -TimeoutSec 10

Note -m 10 and the timeouts: a monitoring call that hangs must not hold up the job it is monitoring.

Sending output with the ping

POST up to 2 KiB of text and it is kept with the ping — the tail of a log, a row count, which file was processed. Anything beyond 2 KiB is cut off.

./backup.sh 2>&1 | tail -c 2000 | curl -fsS -m 10 --data-binary @- https://.../h/<token>/$?

Limits

  • 60 pings a minute per token. Ample for scheduled work; it is there so a loop calling the URL cannot flood us.
  • Keep the token secret: anyone with it can report your job as healthy. If it leaks, edit the monitor to issue a new one.
  • We record the ping that arrives. If the job lies — reporting success from a wrapper that did not check the real result — we believe it. Report $?, not a hardcoded zero.

Last updated 4 October 2026

Still stuck?

If this did not answer your question, tell us and we will fix the page as well as answer you.