5.1How heartbeats work
Every other monitor type calls your service. A heartbeat works the other way round: your job calls obsrvly. Each call is a ping, one HTTP request to the monitor's ping URL. The last part of that URL is the token, a secret code that identifies your monitor.
Create a heartbeat monitor#
- Select + new monitor, then the heartbeat tab. The page title reads new heartbeat monitor.
- Enter a name.
- Set the schedule. Leave cron expression empty for a job that runs every few minutes or hours, or enter the job's cron line (5.2).
- Set the grace (5.3). The form then shows a summary, for example
marked DOWN if no ping arrives within 5m × 2 = 10m. - Choose the alert channels under on failure — notify, then select create monitor →.
- On the monitor page, select copy next to the ping URL. The text above it reads
POST here from your job every <N>s, orPOST here from your job each time it runsfor a cron schedule. - Add a ping to your job (5.5 to 5.9), then test it (5.4).
The URL and the setup snippets appear only after you create the monitor. The monitor page also has ready-made snippets under setup snippets: shell, crontab, Laravel, Sidekiq and Celery.
A heartbeat monitor has no check locations, no timeout and no assertions. The location of each result shows as inbound.
This chapter writes the ping URL as https://<your-ping-url>. Replace it with the full URL you copied. It already ends in /heartbeat/<token>.
States#
| State | When |
|---|---|
| Pending | The monitor exists, but no ping has arrived. The dashboard list shows PEND. |
| Up | The latest ping reported success. |
| Down | The job reported a failure, or no ping arrived by the deadline. |
| Paused | You paused the monitor. Pings get a normal answer but add no result. |
A down result follows your alert policy, like any other monitor (chapter 9, Alerting). The next successful ping brings the monitor back up.
The deadline#
Each heartbeat has a deadline: the time by which the next ping must arrive.
- Interval schedule: the deadline is the last ping plus expected every × grace periods.
- Cron schedule: the deadline is the next scheduled run after the last ping, plus grace seconds.
- Before the first ping, obsrvly counts from the moment you created the monitor.
- Any finish ping restarts the deadline: success,
/failor an exit code. A/startping does not. - Saving a change to the schedule or the grace restarts the deadline from that moment. A new name or new channels do not.
- When you resume a paused monitor, a fresh deadline starts from that moment.
Last check-in and the countdown to the deadline show on the monitor.
Messages you may see#
| Message | Meaning |
|---|---|
job reported failure | The job sent /fail. |
job exited with code 3 | The job sent a non-zero exit code, here 3. |
heartbeat never received; first was due by <time> | The monitor never got a ping, and the first deadline has passed. |
heartbeat overdue: last ping <time>, deadline was <time> | A ping was missed. |
scheduled run (15 2 * * *) overdue: … | A run on a cron schedule was missed. The text names your expression. |
…; a run started at <time> and never finished | Added when the job sent /start but no finish ping. |
Times in messages are in UTC, for example 2026-10-27T10:22:00Z.
Worked timeline#
A job runs every 5 minutes. The monitor uses expected every 5 minutes and grace periods 2. So each ping must come within 10 minutes of the last one. All times are UTC.
| Time | What happens | Result | Next deadline |
|---|---|---|---|
| 10:00 | You create the monitor. | Pending | 10:10 |
| 10:03 | The job pings. | Up | 10:13 |
| 10:08 | The job sends /fail. | Down: job reported failure | 10:18 |
| 10:12 | The job pings. | Up | 10:22 |
| 10:22 | No ping arrives. Shortly after the deadline, obsrvly adds a result. | Down: heartbeat overdue: last ping 2026-10-27T10:12:00Z, deadline was 2026-10-27T10:22:00Z | — |
| 10:40 | The job pings again. | Up | 10:50 |