Heartbeat-уведомления (dead-man-switch)
Heartbeat — пассивный мониторинг по принципу «не позвонил — значит, что-то случилось». Ваш сервис/cron/скрипт регулярно дёргает короткий HTTP-эндпоинт Notifly, а если очередной ping не пришёл за заданное время — вы получаете push-уведомление с настраиваемым текстом.
Это особенно удобно для:
- регулярных cron-задач (бэкапы, импорты, генераторы отчётов);
- демонов, которые «должны быть всегда включены»;
- IoT-устройств, которые «звонят домой»;
- batch-пайплайнов, где важно не «успех», а «успех вовремя».
Как это работает
Заголовок раздела «Как это работает»ваш cron ──ping──▶ POST /heartbeat/ping/<pingToken> (раз в N сек) └─▶ Notifly обновляет last_ping и сдвигает next_check_at
каждую минуту: timer-trigger → notifly-heartbeat-checker └─▶ SELECT WHERE next_check_at <= now AND status IN (ok, pending) для каждого: создать сообщение + push в WS- Хранение — таблица
heartbeatsв YDB Serverless. Один индекс поnext_check_at— checker сканирует только просроченные записи (дёшево). - Проверка — отдельная Cloud Function
notifly-heartbeat, запускается таймер-триггером Yandex Cloud (cron* * * * ? *— раз в минуту). - Уведомление — обычное сообщение Notifly, отправляется через выбранный при создании heartbeat-а канал и доходит до всех ваших клиентов (web, Android, desktop), как любая другая push-нотификация. Если у канала настроена доставка, алерт эскалируется по её методам.
Создание heartbeat
Заголовок раздела «Создание heartbeat»Через админку
Заголовок раздела «Через админку»-
Откройте в app.notifly.ru нужный канал → вкладка Heartbeats → «Создать heartbeat». Тот же диалог открывает мастер создания монитора (Мониторинг → Мониторы, «Что вы хотите проверять?») — карточка «Моя джоба сама шлёт пинг (dead-man switch)».
-
Заполните поля:
- Название — для отображения, например «Cron бэкапа базы».
- Канал — через какой канал слать алёрт. Доступны свои каналы и расшаренные с правом отправки.
- Интервал — ожидаемый период между ping-ами: в форме выбирается из списка (1 мин, 5 мин, 10 мин, 30 мин, 1 ч, 1 д). Произвольное значение от 30 секунд до 30 суток можно задать через REST API.
- Допуск (сек) — сколько ещё ждать после интервала (защита от джиттера), по умолчанию 60.
- Важность — «Критичный», «Обычный» или «Информационный», см. Важность и сводный статус канала.
- Уведомление о падении — заголовок и текст (Markdown) push-а, когда ping пропустит дедлайн. Если оставить пустыми, подставится текст по умолчанию.
- Уведомление о восстановлении — заголовок и текст, которые приходят, когда после алёрта снова пришёл ping (по умолчанию «Сервис вернулся в нормальное состояние.»). Время простоя добавляется автоматически.
Поля приоритета в форме нет — у создаваемого в админке heartbeat-а приоритет alert по умолчанию равен 5. Задать другой приоритет (
alertPriority, 0–10) можно через REST API или MCP. -
Скопируйте ping URL из колонки «URL пинга» таблицы heartbeat-ов (кнопка «Копировать URL»). У каждой строки есть вкладки «История» (график пингов и пропусков) и «Примеры кода» (готовые сниппеты для curl, cron и языков, в том числе ping с причиной).
Через REST API
Заголовок раздела «Через REST API»curl -X POST "$NOTIFLY_URL/heartbeat" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <api-token>" \ -d '{ "appid": 12345, "name": "Cron бэкапа базы", "intervalSec": 3600, "graceSec": 300, "tier": "critical", "alertTitle": "Бэкап не запустился", "alertMessage": "За последний час cron бэкапа не пришёл — проверьте сервер!", "alertPriority": 9, "recoveryTitle": "Бэкап восстановлен", "recoveryMessage": "Бэкап снова работает." }'| Поле | Обязательное | Ограничения и умолчания |
|---|---|---|
appid | да | свой канал или расшаренный с правом отправки; менять нельзя |
name | да | 1–200 символов |
intervalSec | да | 30 – 2 592 000 (30 суток) |
graceSec | — | 0 – 604 800 (7 суток), по умолчанию 0 |
alertMessage | да | 1–2000 символов |
alertTitle | — | заголовок alert-а |
alertPriority | — | 0–10; не передан — 0 |
recoveryTitle | — | заголовок recovery |
recoveryMessage | — | текст recovery; без него test-recovery отвечает 400 |
tier | — | critical, normal или info; не передан — normal |
В ответе будет объект heartbeat-а со статусом pending и pingToken
(начинается с H...); итоговый URL — https://<домен>/heartbeat/ping/<pingToken>.
PUT /heartbeat/:id принимает тот же набор полей и заменяет настройки
целиком: не переданные alertPriority, tier, graceSec сбросятся к
умолчаниям, поэтому присылайте полный объект. Канал при обновлении не меняется:
appid в теле обязателен, но игнорируется.
Через MCP (для AI-ассистента)
Заголовок раздела «Через MCP (для AI-ассистента)»Если у вас настроен MCP-сервер Notifly (см. MCP), просто попросите ассистента:
Создай heartbeat для канала «бэкапы», который ждёт ping раз в час с допуском 5 минут, и шлёт «Бэкап не запустился» приоритета 9.
MCP-инструменты: list_heartbeats, create_heartbeat, update_heartbeat,
pause_heartbeat, resume_heartbeat, delete_heartbeat, ping_heartbeat.
Если приоритет не указан, create_heartbeat ставит 8. Важность (tier)
MCP-инструменты не задают — её меняют в админке или через REST API.
Использование ping
Заголовок раздела «Использование ping»Самый минимальный ping — обычный curl без авторизации:
curl -fsS "$NOTIFLY_URL/heartbeat/ping/<pingToken>" -o /dev/nullpingToken — это и есть аутентификация. Никаких других заголовков не нужно.
Поддерживается и GET, и POST — чтобы можно было дёргать прямо из cron-job
или мониторингов, которые не умеют POST.
| Ответ | Когда |
|---|---|
204 | ping принят (для heartbeat-а на паузе — принят и проигнорирован) |
401 | токен не похож на ping-токен (не начинается с H) |
404 | неизвестный токен |
429 | исчерпана дневная квота событий — ping не засчитан |
Каждый ping — одно событие дневной квоты тарифа, см. Квоты и тарифы.
Причина в ping
Заголовок раздела «Причина в ping»POST может нести JSON {"fail_reason": "..."} (до 500 символов). Если этот
ping выводит heartbeat из alerting, причина прикладывается к
recovery-уведомлению (extras.fail_reason) — рядом с длительностью простоя
(extras.downtime_sec, считается от последнего живого ping-а):
curl -fsS -X POST "$NOTIFLY_URL/heartbeat/ping/H..." \ -H "Content-Type: application/json" \ -d '{"fail_reason": "DB connection timeout after 30s"}'*/15 * * * * /usr/local/bin/backup.sh && curl -fsS "$NOTIFLY_URL/heartbeat/ping/H..." -o /dev/nullPing вызывается только при успехе скрипта (благодаря &&). Если бэкап
упал — ping не пройдёт, и через intervalSec+graceSec прилетит alert.
systemd timer
Заголовок раздела «systemd timer»В service-юните регулярного таймера:
[Service]Type=oneshotExecStart=/usr/local/bin/backup.shExecStartPost=/usr/bin/curl -fsS https://api.notifly.ru/heartbeat/ping/H... -o /dev/nullExecStartPost запускается только если основной ExecStart завершился успешно.
import requests, subprocess
subprocess.check_call(["/usr/local/bin/backup.sh"])requests.post(f"{NOTIFLY_URL}/heartbeat/ping/{TOKEN}", timeout=10)Node.js
Заголовок раздела «Node.js»import {execSync} from 'child_process';execSync('/usr/local/bin/backup.sh');await fetch(`${NOTIFLY_URL}/heartbeat/ping/${TOKEN}`, {method: 'POST'});Состояния heartbeat
Заголовок раздела «Состояния heartbeat»| Состояние | Что значит |
|---|---|
pending | Создан (или снят с паузы), ping ещё не приходил |
ok | Последний ping в пределах интервала |
alerting | Дедлайн пропущен, alert уже отправлен |
paused | Проверка отключена (через UI или POST /heartbeat/:id/pause) |
Дедлайн — intervalSec + graceSec от последнего ping-а (для нового
heartbeat-а — от момента создания). После отправки алёрта heartbeat не
«спамит»: пока он в alerting, повторных алёртов нет, сколько бы ping ни
отсутствовал. Первый ping после alerting вернёт heartbeat в ok и отправит
recovery-уведомление (с тем же приоритетом, что и alert). Переход
pending → ok recovery не шлёт.
Важность и сводный статус канала
Заголовок раздела «Важность и сводный статус канала»У каждого heartbeat-а есть важность (tier), от которой зависит сводный
статус канала:
tier | В админке | Сбой такого heartbeat-а даёт каналу |
|---|---|---|
critical | «Критичный» | alerting («Сбой») |
normal | «Обычный» | alerting («Сбой») |
info | «Информационный» | только degraded («Деградация») |
Сводный статус считается с приоритетом alerting > degraded > pending > ok;
канал без heartbeat-ов — none. Heartbeat-ы на паузе на статус не влияют.
Важность меняет только цвет канала, а не доставку: приоритет алёрта всегда
равен alertPriority.
В админке статус виден цветной точкой у канала в списке, а вкладка Heartbeats на странице канала показывает сводку по каналу и всем его вложенным каналам. Критичные и информационные heartbeat-ы помечены чипом.
Тот же статус отдаёт GET /application/:id/status (с ?recursive=1 — по
всему поддереву канала):
{ "overall": "degraded", "total": 4, "alerting": 0, "alertingCritical": 0, "alertingInfo": 1, "pending": 0, "lastStatusAt": "2026-06-23T09:59:12Z", "heartbeats": [ { "id": 17, "appid": 12345, "name": "Отчёт в Slack", "tier": "info", "status": "alerting", "lastPing": "2026-06-23T07:00:03Z", "owner": "ops@example.com" } ]}alerting считает только critical + normal, alertingCritical — их
подмножество тира critical, alertingInfo — сбои info (в alerting не
входят). Элементы heartbeats — полные объекты heartbeat-ов (как в
GET /heartbeat); в примере показана часть полей.
В сводке расшаренного канала видны heartbeat-ы всех его участников. Чужие
помечены readOnly: true, у них заполнен автор (owner) и пустой
pingToken: менять, ставить на паузу и пинговать heartbeat может только его
автор.
REST API
Заголовок раздела «REST API»| Метод и путь | Авторизация | Назначение |
|---|---|---|
GET /heartbeat | API-токен | список своих heartbeat-ов |
POST /heartbeat | API-токен (write) | создание |
PUT /heartbeat/:id | API-токен (write) | обновление (полная замена полей) |
DELETE /heartbeat/:id | API-токен (write) | удаление |
POST /heartbeat/:id/pause | API-токен (write) | приостановить проверки |
POST /heartbeat/:id/resume | API-токен (write) | возобновить проверки (статус pending) |
POST /heartbeat/:id/test-alert | API-токен (write) | отправить тестовый alert немедленно (статус не меняется) |
POST /heartbeat/:id/test-recovery | API-токен (write) | отправить тестовый recovery немедленно (статус не меняется) |
GET /application/:id/status | API-токен | сводный статус heartbeat-ов канала (?recursive=1) |
GET/POST /heartbeat/ping/:token | публичный | ping (token = аутентификация) |
Заголовок — X-Notifly-Key: <api-token>, см. API-токены.
Тестовые уведомления приходят с префиксом [ТЕСТ] в заголовке.
Почему YDB, а не S3?
Заголовок раздела «Почему YDB, а не S3?»Архитектурно проверка «у каких heartbeat-ов next_check_at ≤ сейчас» — это
индексный point-query, а не сканирование. В YDB Serverless у нас
INDEX heartbeats_next_check_idx и одна короткая транзакция на минуту
(несколько Request Units). В S3 пришлось бы делать LIST (1 запрос) +
GET (по запросу на каждый объект), причём оплата у Object Storage идёт
за каждый запрос, и таких запросов было бы много даже без алёртов.
Heartbeat-ов в проекте быстро становится десятки и сотни — поэтому YDB
заметно дешевле и быстрее.