Heartbeat notifications (dead-man-switch)
Heartbeat — passive monitoring by the principle “if it didn’t call, something happened”. Your service/cron/script regularly hits a short HTTP endpoint in Notifly, and if the next ping doesn’t arrive within the configured time — you receive a push notification with a customizable text.
This is especially convenient for:
- regular cron jobs (backups, imports, report generators);
- daemons that “must be always on”;
- IoT devices that “call home”;
- batch pipelines where the important thing is not just “success” but “success on time”.
How it works
Section titled “How it works”your cron ──ping──▶ POST /heartbeat/ping/<pingToken> (every N sec) └─▶ Notifly updates last_ping and shifts next_check_at
every minute: timer-trigger → notifly-heartbeat-checker └─▶ SELECT WHERE next_check_at <= now AND status IN (ok, pending) for each: create message + push to WS- Storage — table
heartbeatsin YDB Serverless. One index onnext_check_at— the checker scans only overdue records (cheap). - Checking — a separate Cloud Function
notifly-heartbeat, triggered by Yandex Cloud timer trigger (cron* * * * ? *— once a minute). - Notification — a normal Notifly message, sent via the channel chosen when creating the heartbeat and delivered to all your clients (web, Android, desktop), like any other push notification.
Creating a heartbeat
Section titled “Creating a heartbeat”Via the admin UI
Section titled “Via the admin UI”- Open app.notifly.ru → Heartbeats.
- Click “Create heartbeat”, fill in:
- Name — for display, e.g. “DB backup cron”.
- Channel — which channel to send the alert through.
- Interval (sec) — expected period between pings (minimum 30).
- Grace (sec) — how long to wait after the interval (jitter protection).
- Alert text — what will arrive in the push when a ping misses the deadline.
- Alert priority — 0–10, default 8 (loud notification).
- Recovery text — optional “everything is OK again” message when a ping arrives after an alert.
- Copy the Ping URL from the table — your script will hit this URL.
Via the REST API
Section titled “Via the REST API”curl -X POST "$NOTIFLY_URL/heartbeat" \ -H "Content-Type: application/json" \ -H "X-Gotify-Key: <client-token>" \ -d '{ "appid": 12345, "name": "Cron бэкапа базы", "intervalSec": 3600, "graceSec": 300, "alertTitle": "Бэкап не запустился", "alertMessage": "За последний час cron бэкапа не пришёл — проверьте сервер!", "alertPriority": 9, "recoveryMessage": "Бэкап снова работает." }'The response will contain pingToken (starts with H...) and the final URL like
https://<domain>/heartbeat/ping/<pingToken>.
Via MCP (for AI assistant)
Section titled “Via MCP (for AI assistant)”If you have a Notifly MCP server configured (see MCP), just ask the assistant:
Create a heartbeat for the “backups” channel that expects a ping every hour with a 5-minute grace period, and sends “Backup didn’t run” with priority 9.
MCP tools: list_heartbeats, create_heartbeat, update_heartbeat,
pause_heartbeat, resume_heartbeat, delete_heartbeat, ping_heartbeat.
Using ping
Section titled “Using ping”The most minimal ping is a plain curl without authentication:
curl -fsS "$NOTIFLY_URL/heartbeat/ping/H<pingToken>" -o /dev/nullpingToken itself is the authentication. No other headers are needed.
Both GET and POST are supported — so you can hit it directly from a cron job
or monitoring systems that don’t support POST.
*/15 * * * * /usr/local/bin/backup.sh && curl -fsS "$NOTIFLY_URL/heartbeat/ping/H..." -o /dev/nullThe ping is called only on success of the script (thanks to &&). If the backup
fails — the ping won’t run, and after intervalSec+graceSec an alert will arrive.
systemd timer
Section titled “systemd timer”In the service unit of a regular timer:
[Service]Type=oneshotExecStart=/usr/local/bin/backup.shExecStartPost=/usr/bin/curl -fsS https://your-notifly/heartbeat/ping/H... -o /dev/nullExecStartPost runs only if the main ExecStart finished successfully.
Python
Section titled “Python”import requests, subprocess
subprocess.check_call(["/usr/local/bin/backup.sh"])requests.post(f"{NOTIFLY_URL}/heartbeat/ping/{TOKEN}", timeout=10)Node.js
Section titled “Node.js”import {execSync} from 'child_process';execSync('/usr/local/bin/backup.sh');await fetch(`${NOTIFLY_URL}/heartbeat/ping/${TOKEN}`, {method: 'POST'});Heartbeat states
Section titled “Heartbeat states”| State | Meaning |
|---|---|
pending | Created, no pings have arrived yet |
ok | Last ping was within the interval |
alerting | Deadline missed, alert has already been sent |
paused | Checking is disabled (via UI or POST /heartbeat/:id/pause) |
After sending an alert the heartbeat does not “spam” — the next check is postponed
by another intervalSec + graceSec seconds. When the first ping arrives after alerting —
the heartbeat returns to ok and (if configured) a recovery notification is sent.
REST API
Section titled “REST API”| Method & path | Authorization | Purpose |
|---|---|---|
GET /heartbeat | client-token | list heartbeats |
POST /heartbeat | client-token | create |
PUT /heartbeat/:id | client-token | update |
DELETE /heartbeat/:id | client-token | delete |
POST /heartbeat/:id/pause | client-token | pause checks |
POST /heartbeat/:id/resume | client-token | resume checks |
GET/POST /heartbeat/ping/:token | public | ping (token = authentication) |
Why YDB, not S3?
Section titled “Why YDB, not S3?”Architecturally, the check “which heartbeats have next_check_at ≤ now” is
an indexed point-query, not a scan. In YDB Serverless we have
INDEX heartbeats_next_check_idx and one short transaction per minute
(a few Request Units). In S3 you’d have to do a LIST (1 request) +
a GET (a request per object), and Object Storage charges per request, and there would be many requests even without alerts.
Heartbeats in a project quickly become tens or hundreds — so YDB
is noticeably cheaper and faster.