A heartbeat monitor watches a job that runs on a schedule, such as a backup, an import or a queue worker. Instead of AgileMonitor checking a URL, the job requests the monitor's ping URL each time it finishes. If no ping arrives within the check interval, the monitor goes down and its contacts are alerted.

## Set one up
1. Create a monitor with `type` `heartbeat`. Its `heartbeat_url` is in the response, and on the monitor's page.
2. Make the job request that URL after each successful run. GET, HEAD and POST all count.
3. Make the check interval longer than the job's period. There is no grace period: a job that runs every hour and takes a few minutes needs more than 60 minutes. For a daily job, set `check_interval_seconds`, for example `90000` (25 hours).

Treat the URL as a secret: anyone who has it can ping the monitor. Keep it in an environment variable rather than in your repository, and out of chat and CI logs: chat apps that preview links request them, which counts as a ping.

## Examples
### Cron

```bash
0 2 * * * /usr/local/bin/backup.sh && curl -fsS --retry 3 -o /dev/null "$AGILEMONITOR_PING_BACKUP"
```

### Laravel scheduler

```php
Schedule::command('backup:run')->daily()->pingOnSuccess(config('services.agilemonitor.pings.backup'));
```

### systemd timer

```ini
[Service]
ExecStart=/usr/local/bin/backup.sh
ExecStartPost=/usr/bin/curl -fsS --retry 3 -o /dev/null ${AGILEMONITOR_PING_BACKUP}
```

### GitHub Actions

```yaml
- name: Tell AgileMonitor the nightly job ran
  if: success()
  run: curl -fsS --retry 3 -o /dev/null "${{ secrets.AGILEMONITOR_PING_NIGHTLY }}"
```

### Node.js

```js
await fetch(process.env.AGILEMONITOR_PING_IMPORT, { method: "POST" });
```

## Send a heartbeat
`POST /ping/{token}`

`GET /ping/{token}` (HEAD works too)

Records that the job ran. Request the monitor's `heartbeat_url` after every successful run; the monitor goes down when no ping arrives within its check interval. The body is ignored, and GET and HEAD work the same way. A paused or deleted monitor answers 404. Anyone with the URL can ping, and chat apps that preview links request them, so keep it out of chat and logs.

| | |
|---|---|
| Authentication | None. The token in the path is the credential |
| Rate limit | 60 requests per minute per token and 300 per minute per IP address |

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `token` | path | string | Yes | The heartbeat monitor's `heartbeat_token`. Anyone with it can ping the monitor. |

### Example request

```bash
curl -X POST https://monitor.agilepixel.io/ping/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
```

### Responses

| Status | When | Body |
|---|---|---|
| `204` | The ping was recorded. There is no body. | None |
| `404` | No active heartbeat monitor has that token. It is wrong, or the monitor is paused or deleted. | [Problem](https://monitor.agilepixel.io/docs/api/errors) |
| `429` | The rate limit is used up. Wait for `Retry-After` seconds. | [Problem](https://monitor.agilepixel.io/docs/api/errors) |
| `500` | Something went wrong on our side. Try again; if it persists, contact support. | [Problem](https://monitor.agilepixel.io/docs/api/errors) |

#### 204

No body.

#### 404

```json
{"type": "about:blank", "title": "Not Found", "status": 404}
```

#### 429

```json
{"type": "about:blank", "title": "Too Many Requests", "status": 429}
```

#### 500

```json
{"type": "about:blank", "title": "Internal Server Error", "status": 500}
```
