
Вебхук — самый универсальный клей в мониторинге: сайт клиента упал — POST прилетел в ваш n8n, оттуда в тикет-систему, оттуда в Slack-канал студии. Никакого интерфейса, никакого ручного клика — просто HTTP-запрос туда, куда вы скажете. Но у этой простоты есть обратная сторона: ваш обработчик получает POST от кого угодно, кто знает URL. Откуда ему знать, что это правда Pingvera, а не кто-то подделал тело запроса и прислал фальшивый «инцидент»?
Ответ — подпись. Разберём, как она устроена в Pingvera, что реально приходит в заголовках и как её проверить, не наступив на типичные грабли.
Канал доставки типа webhook при каждом событии алертинга — открытие инцидента, закрытие, тестовое сообщение, недельный CMS-дайджест — шлёт на настроенный адрес обычный POST с JSON-телом и Content-Type: application/json. Никакой магии: это тот же формат, что принимают n8n, Zapier-аналоги или ваш собственный маленький обработчик на пяти строчках. Доставка ждёт ответ 10 секунд, любой статус ≥300 — неудача, которая ретраится с экспоненциальным backoff до 8 попыток (суммарно около 2 часов 15 минут), а затем помечается окончательно неудавшейся.
В теле — плоский JSON без вложенностей, специально так, чтобы его было легко разобрать в любом no-code-инструменте:
{
"event": "opened",
"severity": "critical",
"summary": "host offline",
"incident": "inc_01J...",
"org": "ООО Ромашка",
"entity": "acme.com · Доступность",
"target": "https://acme.com",
"link": "https://app.pingvera.ru/#/incidents/inc_01J...",
"started_at": "2026-07-21T10:15:00Z"
}
event — одно из opened / resolved / test / digest; incident пустой у test/digest; link ведёт прямо на карточку в дашборде — удобно вставлять кликабельной кнопкой в тикет или Telegram-сообщение, которое собирает ваш сценарий.
Вебхук-адрес рано или поздно светится: в логах n8n, в конфиге интеграции, в скриншоте на созвоне. Если обработчик доверяет любому телу, которое на него пришло, — он доверяет не Pingvera, а факту знания URL кем угодно. Для сценария «упал сайт → тикет клиенту» это не абстрактный риск: поддельный opened с чужим доменом в поле entity — это ложная тревога, которая уходит клиенту студии от её же имени.
Решение — не прятать URL глубже, а подписывать каждое тело секретом, которого нет ни у кого, кроме Pingvera и вас.
Каждый webhook-канал получает секрет при создании — он приходит один раз, в ответе POST /api/v1/channels (поле secret, префикс whsec_), и больше нигде в API не отдаётся. Как и API-токен — если потеряли, пересоздаёте канал, а не «смотрите ещё раз в настройках».
Пока секрет у канала есть, к каждому запросу добавляются два заголовка:
X-Pingvera-Signature: sha256=<hex>
X-Pingvera-Timestamp: <unix-секунды>
X-Pingvera-Timestamp — просто время отправки, в саму подпись не входит, информационное поле. Вся защита — в X-Pingvera-Signature: это HMAC-SHA256(secret, raw_body) в hex, где raw_body — ровно те байты, что легли в тело запроса.
Здесь есть одна практическая ловушка, из-за которой проверка «не сходится» чаще всего: HMAC нужно считать от сырых байтов тела, а не от JSON, который вы пересобрали после парсинга. Порядок ключей, пробелы, экранирование юникода — всё это может отличаться после повторной сериализации, и хэш просто не совпадёт, хотя данные те же. Читайте тело как поток байт до json.Unmarshal/json_decode, считайте HMAC от него, и только потом парсите.
Вторая ловушка — сравнение через обычный ==. Оно завершается на первом несовпадающем байте, и по времени ответа теоретически можно подобрать правильную подпись. Сравнивайте в constant-time: hmac.Equal в Go, hash_equals в PHP, hmac.compare_digest в Python.
Пример на Go:
func verify(secret string, rawBody []byte, header string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
want := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(want), []byte(header))
}
На Python — то же самое, только тело нужно брать до парсинга (request.get_data() во Flask отдаёт именно сырые байты):
def verify(secret: str, raw_body: bytes, header: str) -> bool:
mac = hmac.new(secret.encode(), raw_body, hashlib.sha256)
want = "sha256=" + mac.hexdigest()
return hmac.compare_digest(want, header)
На PHP аналогично: file_get_contents('php://input') вместо уже распарсенного $_POST, и hash_equals вместо ===.
Подпись защищает получателя от подделки. Но есть и обратная сторона — защита отправителя от того, чтобы кто-то через настройку webhook-URL заставил Pingvera постучаться во внутреннюю сеть. Поэтому адреса из приватных диапазонов (localhost, 10.0.0.0/8 и подобные) как цель webhook-доставки заблокированы — это защита от SSRF, а не ограничение по недосмотру.
Webhook-каналы, созданные до появления подписи, продолжают получать те же уведомления — просто без заголовков X-Pingvera-Signature/X-Pingvera-Timestamp. Ничего не сломалось и ломать не планируется: обратная совместимость важнее, чем принудительная миграция чужих интеграций. Если хочется подписанные уведомления на существующий сценарий — пересоздайте канал, секрет придёт при создании нового.
Отдельно: Slack и Discord используют свой формат incoming-webhook ({"text": …} / {"content": …}), и секрета подписи там нет — эта схема касается только каналов типа webhook, куда Pingvera шлёт собственный JSON.
Для студии, которая ведёт мониторинг десятка клиентских сайтов, вебхук с подписью — это точка входа в то, что уже есть: сценарий в n8n, который по opened заводит карточку в Trello и шлёт клиенту сообщение с человеческим текстом из entity/summary; Zapier-аналог, кладущий инцидент в общую таблицу учёта SLA; или просто эндпоинт на своём сервере, который решает, кому из дежурных постучаться. Подпись в этой схеме — не бюрократия, а ровно то, что позволяет доверять автоматике: обработчик может смело исполнять действие («открыть тикет», «уведомить клиента») именно потому, что заранее проверил — тело реально от Pingvera.
Сам канал заводится тем же write-API, которым создаются мониторы и статус-страницы, или командой channels create из CLI — вебхук с подписью заводится на онбординге клиента вместе с остальным мониторингом, без единого клика в дашборде.
Вебхук без подписи — это удобно, но требует слепого доверия к URL. Вебхук с подписью снимает это допущение: секрет выдаётся один раз при создании канала, каждое тело идёт с HMAC-SHA256 от секрета, а проверка на вашей стороне — десяток строк на любом языке, если считать её от сырых байт и сравнивать в constant-time. Старые каналы без секрета работают как раньше — подпись добавляется, а не заменяет существующее поведение.
Без подписи обработчик обязан верить любому POST на этот URL, а адрес легко угадать или подсмотреть в логах. Подпись доказывает, что тело создано тем, у кого есть секрет (Pingvera), и не менялось по пути — защита от подделанных «инцидентов» и от случайного мусора на публичном эндпоинте.
Только сырые байты тела, как они пришли по HTTP, — до json_decode/json.Unmarshal. Если пересобрать JSON заново и посчитать HMAC от него, подпись почти всегда не сойдётся: порядок ключей и пробелы после повторной сериализации могут отличаться от оригинала.
Они продолжат работать как раньше и просто не получат заголовки X-Pingvera-Signature/X-Pingvera-Timestamp — доставка не ломается. Секрет выдаётся только новым webhook-каналам и показывается один раз при создании; чтобы получить подписанные уведомления на старый URL, пересоздайте канал.
Pingvera шлёт алерты по email, Telegram, Max — и вебхуком с подписью в n8n, Zapier-аналоги или ваш собственный обработчик. Секрет выдаётся при создании канала, проверка — несколько строк на Go, PHP или Python. Базовый тариф с вебхуками — бесплатно до 5 сайтов.
Подключить вебхук бесплатноЧитайте также: Статус-страница, которую читают не только люди и Мониторинг в вашем ИИ-ассистенте: подключаем Claude или Cursor к Pingvera за минуту.