> ## Documentation Index
> Fetch the complete documentation index at: https://docs.happyuptime.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Heartbeat monitoring for cron jobs

Detect when your nightly job stops running. Push, don't poll.

# Heartbeat monitoring for cron jobs

A heartbeat monitor flips the model: instead of Happy Uptime calling out, your job calls in. If the call doesn't arrive within the expected window, the monitor goes `down` and alerts fire.

## When to use it

- Cron jobs that should run every N minutes/hours/days.
- ETL pipelines.
- Background workers that should be processing items.
- Anything internal that Happy Uptime's edge can't reach.

## Setup

**Create the monitor**

Pick **type: heartbeat**. Set:
    - `heartbeat_url` — a unique slug, e.g. `nightly-etl-prod`. This becomes your ping URL.
    - `heartbeat_interval` (seconds) — how often you expect the job to ping.
    - `heartbeat_grace` (seconds) — how long after a missed ping before going `down`. Cover normal job duration.

    ```bash
    happy monitors create \
      --type heartbeat \
      --name "Nightly ETL" \
      --heartbeat-url nightly-etl-prod \
      --heartbeat-interval 86400 \
      --heartbeat-grace 1800
    ```

  **Ping from your job**

At the **end** of every successful run, POST to:

    ```
    https://happyuptime.com/api/heartbeat/{your-heartbeat-url}
    ```

    No auth required (the slug is the secret).

    ```bash
    # Last line of your cron script
    curl -fsS -X POST https://happyuptime.com/api/heartbeat/nightly-etl-prod
    ```

    Or with a status payload:

    ```bash
    curl -X POST https://happyuptime.com/api/heartbeat/nightly-etl-prod \
      -H "Content-Type: application/json" \
      -d '{ "status": "ok", "message": "Processed 1247 records" }'
    ```

    Send `"status": "fail"` when the job ran but failed — the monitor goes `down` immediately and the message shows in the alert.

  **Verify with a test ping**

Run the curl manually. Check the dashboard within 30s — you should see a green `up` status and the message.

## Patterns

**cron / system timer**

Append the curl to the cron line:
    ```cron
    0 2 * * * /usr/local/bin/run-etl && curl -fsS -X POST https://happyuptime.com/api/heartbeat/nightly-etl-prod
    ```
    `&&` ensures the ping only fires on success. Use `; curl ...` to ping regardless of exit code.

  **Node.js script**

```js
    async function main() {
      try {
        await runJob();
        await fetch(`https://happyuptime.com/api/heartbeat/${process.env.HEARTBEAT_SLUG}`, {
          method: "POST",
          body: JSON.stringify({ status: "ok" }),
          headers: { "Content-Type": "application/json" },
        });
      } catch (err) {
        await fetch(`https://happyuptime.com/api/heartbeat/${process.env.HEARTBEAT_SLUG}`, {
          method: "POST",
          body: JSON.stringify({ status: "fail", message: String(err) }),
          headers: { "Content-Type": "application/json" },
        });
        process.exit(1);
      }
    }
    main();
    ```

  **GitHub Actions**

```yaml
    - name: Run job
      run: ./script.sh
      id: job
    - name: Ping heartbeat (success)
      if: success()
      run: curl -fsS -X POST https://happyuptime.com/api/heartbeat/${{ secrets.HB_SLUG }}
    - name: Ping heartbeat (failure)
      if: failure()
      run: |
        curl -X POST https://happyuptime.com/api/heartbeat/${{ secrets.HB_SLUG }} \
          -H "Content-Type: application/json" \
          -d '{"status":"fail","message":"GitHub Actions job failed"}'
    ```

## How "down" is detected

The cron scheduler checks every minute: for any heartbeat monitor whose `last_checked_at` is older than `heartbeat_interval + heartbeat_grace`, mark it `down`. Same alert pipeline as any other monitor — Slack page, escalation, status page incident.

## Choosing the interval and grace

| Job runs every | Recommended `heartbeat_interval` | Recommended `heartbeat_grace` |
|---|---|---|
| Hour | 3600 | 600 (10 min) |
| Day | 86400 | 1800 (30 min) |
| Week | 604800 | 7200 (2 hours) |

Grace should comfortably cover the longest the job has ever taken. If your daily ETL usually finishes in 15 minutes but occasionally takes 45, set grace to at least 60 minutes.