Heartbeat monitors
A heartbeat watches a job you run yourself, such as a nightly backup, a cron job or a worker. Nothing is probed: your job requests a URL when it runs, and CuliPulse alerts you when the check-in doesn’t arrive. No agent is needed.
Set it up
Section titled “Set it up”- Go to Monitors, click New monitor and choose Heartbeat (or the Cron job check-in template).
- Say how often the job should run (Expect a ping every) and how late a check-in may be (Alert me if it’s late by), then save.
- The monitor opens on its check-in URL and a ready-to-copy snippet for crontab, a shell script, Python, Node.js, Kubernetes or GitHub Actions.
- Test the connection. Run
curl -fsS <your check-in URL>once from the server where the job lives. It should printOK, and the monitor’s page shows First check-in received. - Add the snippet to your job. Replace
/path/to/your-jobwith your command.
The check-in URL
Section titled “The check-in URL”Your monitor’s check-in URL looks like https://culipulse.dev/ping/<your-token>. Treat it like a
password: anyone who has it can mark the job healthy. You can rotate it any time, see
Managing monitors.
| Request | Meaning |
|---|---|
https://culipulse.dev/ping/<your-token> |
The run finished and worked. |
https://culipulse.dev/ping/<your-token>/<exit code> |
The run finished. 0 worked; any other number marks the monitor down right away. This is what the snippets send (the job’s own exit code). |
https://culipulse.dev/ping/<your-token>/fail |
The run failed. The monitor goes down right away. This can’t be turned off. |
https://culipulse.dev/ping/<your-token>/start |
The run began. Optional; only needed to time runs (see below). |
Any of GET, POST or HEAD works.
When it alerts
Section titled “When it alerts”- Down: a heartbeat goes down when no check-in arrives by the last check-in + the interval + the grace period (counted from your last schedule change instead, if that is later), or when a run reports a failure. There is no “alert after N failures” setting. To tolerate a late run, make the grace period longer.
- Degraded: with a max run time set, when a run that sent
/startis still going at the limit, or finishes after it (see Time your runs). It is immediate: there is no wait for repeated breaches. - Back up: the next successful check-in brings it back up. If the monitor was only ever degraded (it never went down), the recovery alert says Back to normal instead, because nothing was down.
- No alert for the first check-in. The first check-in of a new heartbeat, and the first one after you resume a paused heartbeat, turn it up without an alert. Nothing was down, so nothing “came back”.
Pick the right schedule
Section titled “Pick the right schedule”The interval you set is how often your job should run. The crontab and Kubernetes snippets on the monitor’s page already carry the matching schedule, and they change if you change the interval.
| Interval | Crontab schedule in the snippet |
|---|---|
| 1 minute | * * * * * |
| 5 minutes | */5 * * * * |
| 15 minutes | */15 * * * * |
| 30 minutes | */30 * * * * |
| 1 hour | 0 * * * * |
| 6 hours | 0 */6 * * * |
| 12 hours | 0 */12 * * * |
| 1 day | 0 3 * * * (every day at 03:00 server time, change the hour to suit) |
| 1 week | 0 3 * * 0 (Sundays at 03:00) |
- Under one minute: cron can’t run a job more often than once a minute, so the crontab tab gives you a small shell loop instead. In Kubernetes, a CronJob can’t go below a minute either: raise the monitor’s interval to 1 minute, or run the job as a loop.
- An interval cron can’t express exactly (7 minutes, 90 minutes, 3 days): the snippet uses the nearest schedule that is not slower than your interval and says so in a comment. Adjust it to your job’s real schedule.
- Pasting the line unchanged on a monitor with a different interval makes it flap. The monitor expects a check-in every interval, so a job that runs once an hour on a 5-minute monitor goes down and back up twice an hour.
Changing the schedule
Section titled “Changing the schedule”You can change a running heartbeat’s interval or grace period at any time. The next check-in is then due one full new interval plus grace period after you save, so an edit never marks a healthy job down, and a shorter schedule starts counting right away.
Grace period and clock changes
Section titled “Grace period and clock changes”The grace period is how late a check-in may be before it counts as missed. When you create a
heartbeat in the app, it starts at the larger of 10 minutes or 20% of the interval (a 1-day
heartbeat starts with 5 hours) and follows the interval until you pick your own. Via the API or
Terraform, a missing grace_seconds defaults to the larger of 60 seconds or 20% of the interval, at most 30 days.
Existing heartbeats keep the grace they have.
The grace period must be a whole number of seconds from 1 to 2,592,000 (30 days), and the interval can be at most 365 days. A value outside those ranges is refused when you save.
If your job runs at a fixed local time, the night the clocks go back is 25 hours long. A grace period of an hour or more keeps that night from raising a false alert.
Report failures
Section titled “Report failures”Add the job’s exit code to the end of the URL: 0 means it worked, anything else marks the monitor
down right away. In crontab:
0 3 * * * /path/to/your-job; curl -fsS -m 10 --retry 3 https://culipulse.dev/ping/<your-token>/$?Why one request: if the check-in itself is lost (for example a brief network blip), nothing
arrives, so CuliPulse treats the run as late, not failed. The older pattern
your-job && curl <url> || curl <url>/fail reports a failure whenever the success check-in can’t
be sent, even though the job worked. If you copied it before, switch to the snippet.
The snippets are written so a failed check-in can never break your job: the shell and Kubernetes
ones end with || true, the GitHub Actions step is continue-on-error: true, and the Node.js one
gives the request a 10-second timeout.
Time your runs
Section titled “Time your runs”Set Alert if a run takes longer than (under Advanced in the form). The limit is measured
from the /start request to the finishing check-in, so the job has to send both. Once a
limit is set, the snippets on the monitor’s page add the /start line for you:
0 3 * * * curl -fsS -m 10 --retry 3 https://culipulse.dev/ping/<your-token>/start; /path/to/your-job; curl -fsS -m 10 --retry 3 https://culipulse.dev/ping/<your-token>/$?- Still going at the limit: if the finishing check-in hasn’t arrived when the limit passes, the monitor goes degraded right then, without waiting for the run to end. The alert says what CuliPulse saw, for example Run started 14:03 UTC, no finish check-in within 1h.
- It finishes late: the run’s finishing check-in brings the monitor back up, even if it comes after the limit. If the job sends a failure instead, the monitor goes down as usual. If the monitor was already degraded when the limit passed, a late finish is marked ran too long and it stays degraded.
- It never finishes: if no check-in at all arrives by the normal deadline (the last check-in + interval + grace), the same incident turns down as a missed check-in. If the max run time is longer than the time left before that deadline, the deadline comes first: the monitor goes straight to down (missed check-in) without a degraded step.
- Before the very first check-in the heartbeat isn’t watched yet, so a first run that starts and never finishes isn’t alerted. A first run that finishes over the limit is still marked degraded.
- Without a
/startrequest the limit does nothing: CuliPulse can’t know when a run began.
Troubleshooting
Section titled “Troubleshooting”- Still Pending after a while: no check-in has reached CuliPulse yet. Run the test command from
the server that runs the job. If it fails, check that the server can reach
culipulse.devand that the URL is copied in full. - Goes down and up again and again: the job runs less often than the interval. Match the schedule to the interval (see above), or raise the interval.
- Goes down right after you test the snippet: the placeholder
/path/to/your-jobdoesn’t exist, so the snippet sent a failure (exit code 127). Replace it with your real command. - Check-ins stop after you rotate the URL: update the job to use the new URL.