boltZharp Docs
scheduleMonitors

Cron Jobs & Heartbeats

Your job calls a URL when it finishes. If the call doesn't arrive on time, Zharp opens an incident.

A cron job monitor is passive: Zharp never connects to your server. The job sends a heartbeat — an HTTP request to its own ping URL — and Zharp works out when the next one is due. That makes it the right monitor for backups, imports, queue workers, scheduled CI runs and anything else that should happen on a schedule, wherever it runs, as long as it can make an outbound HTTPS request.

Create a cron job monitor

  1. 1
    Pick the type

    On the Monitors page click New, open Monitor type and choose Cron job monitor.

  2. 2
    Copy the ping URL

    The form shows a Ping endpoint box with the monitor's URL already reserved, so you can copy it before saving. It looks like this:

    https://api.zharp.io/api/v1/ping/m_3f9c2a71d04be8a6

    The last part is the monitor's token. Anyone holding the URL can send heartbeats for this monitor, so keep it out of public repositories — store it as a secret in CI.

  3. 3
    Tell Zharp the schedule

    The expected schedule defaults to every 5 minutes. To change it, open Advanced settings and go to Cron settings — see Schedule and grace period.

  4. 4
    Save, then add the ping to your job

    Click Create monitor, then add the URL to the end of the job — examples below. The first heartbeat is due one schedule after you save, plus the grace period, so the monitor waits for your job before it can be marked missed. You can find the full URL again by editing the monitor.

The ping URL

Send a GET or a POST to https://api.zharp.io/api/v1/ping/<token>. No API key or header is needed — the token in the path identifies the monitor. Each request records one successful run.

OptionWhat it does
?host=NAMENames the machine the job ran on. Shown as Host in the run's details. An X-Host header does the same.
POST bodyKept as the run's output and shown under OUTPUT in its details — the last 4,000 characters. Send it as JSON (Content-Type: application/json); a plain-text body is not kept.

Responses

  • 200 — the heartbeat was recorded. The JSON body's data.nextExpected is the time the next heartbeat is due, grace period included.
  • 404 — no monitor has that token. Check you copied the whole URL.
lightbulb
Use curl -fsS --retry 3

-f makes curl exit non-zero on an error response, -sS keeps it quiet unless something goes wrong, and --retry 3 rides out a brief network blip so a single dropped request doesn't become a missed run.

Start, failure and exit codes

The plain URL only says “the job ran”. Add a suffix to say more — each works with GET or POST, and takes ?host= and a body like the plain URL:

URLWhat it records
/ping/<token>/startThe job has started. Nothing is checked yet; the next success or failure is timed from this, and its duration shows on the run.
/ping/<token>/failThe job failed. The monitor goes down straight away with Job failed with exit code 1 and an incident opens — it doesn't wait for the deadline.
/ping/<token>/successThe job succeeded — the same as the plain URL.
/ping/<token>/<exit code>The job ended with this exit code, 0–255. 0 is a success; anything else is a failure with that code in the message.

Anything else after the token answers 400 and records nothing.

A failed run shows as FAILED in Run history, apart from a MISSED one that never reported. Either way the next successful heartbeat brings the monitor back up.

Report start and exit code
#!/bin/sh
PING_URL="https://api.zharp.io/api/v1/ping/YOUR_TOKEN"

# Mark the start, so the run's duration is measured.
curl -fsS --retry 3 "$PING_URL/start" > /dev/null

/usr/local/bin/backup.sh
code=$?

# Report how it ended: 0 is a success, anything else a failure with that exit code.
curl -fsS --retry 3 "$PING_URL/$code" > /dev/null

Schedule and grace period

Under Advanced settings → Cron settings, Expected schedule takes either a simple interval or a cron expression — switch with Use cron expression / Use a simple interval.

SettingChoicesDefault
Expected scheduleEvery n minutes, hours or days — or a standard five-field cron expression such as */5 * * * *Every 5 minutes
Grace period1, 5, 10 or 30 minutes, or 1 hour5 minutes
TimezoneAny IANA timezoneUTC

Each heartbeat sets the deadline for the next one:

  • Interval: the next heartbeat is due one interval after this one arrived, plus the grace period. The clock starts from the last ping, not from the top of the hour.
  • Cron expression: the next heartbeat is due at the expression's next run time after this ping, in the chosen Timezone, plus the grace period. Use this for schedules an interval can't express, such as weekdays only or 02:00 local time.

Set the grace period a little longer than the job usually takes. The ping comes at the end of the run, so a job that takes 20 minutes needs more than 5 minutes of grace. An expression that can't be parsed is rejected when you save, with Cron expression is not valid, e.g. */5 * * * *.

Examples

Replace YOUR_TOKEN with the token from your monitor, or paste the whole URL from the form.

crontab
# Run the backup every night at 02:00, then tell Zharp it finished.
# && means the ping is only sent when the job exits 0.
0 2 * * * /usr/local/bin/backup.sh && curl -fsS --retry 3 https://api.zharp.io/api/v1/ping/YOUR_TOKEN > /dev/null
Shell script, with host and output
#!/bin/sh
# Stop at the first failing command, so a failed run never pings.
set -e

PING_URL="https://api.zharp.io/api/v1/ping/YOUR_TOKEN"

/usr/local/bin/backup.sh > /tmp/backup.log 2>&1

# Ping with the machine's name and the end of the job's log as the run's output.
tail -c 4000 /tmp/backup.log \
  | jq -Rs '{output: .}' \
  | curl -fsS --retry 3 -X POST \
      -H "Content-Type: application/json" \
      --data-binary @- \
      "$PING_URL?host=$(hostname)"
GitHub Actions — store the ping URL as the secret ZHARP_PING_URL
# .github/workflows/nightly.yml
on:
  schedule:
    - cron: "0 2 * * *"

jobs:
  nightly:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/nightly-report.sh
      # Only reached when every step before it succeeded.
      - name: Ping Zharp
        run: curl -fsS --retry 3 "${{ secrets.ZHARP_PING_URL }}?host=github-actions"
PowerShell
# Windows Task Scheduler: run the job, then ping only if it succeeded.
& C:\jobs\export.ps1
if ($?) { Invoke-RestMethod -Uri "https://api.zharp.io/api/v1/ping/YOUR_TOKEN" -Method Get | Out-Null }
info

In these examples the ping only goes out when the job succeeded, so a failed job shows up like one that never ran: the monitor goes down once the grace period is over. To be told about a failure the moment it happens, report the exit code instead — see Start, failure and exit codes.

When it goes down

Zharp looks for overdue monitors every 10 seconds. When the deadline set by the last heartbeat has passed, it records a failed check whose message starts Heartbeat missed, marks the monitor down and opens an incident, which alerts the people and channels on the monitor.

The next heartbeat brings it back up and resolves the incident. On the monitor's page, Run history shows each run as SUCCESS, FAILED or MISSED, and the Schedule card shows the schedule, timezone and grace period it is checked against.