РазделыWebhook

Документация

Webhook

Канал webhook шлёт JSON-запрос на ваш HTTPS-адрес при каждом событии срабатывания. Доступен на Pro и выше. Как подключить канал, см. Уведомления.

На этой странице

Запрос

POST на адрес канала, Content-Type: application/json, User-Agent: SmartAlert-Webhook/1. Таймаут 5 секунд, редиректы не выполняются.

X-SmartAlert-Timestamp
Время отправки, Unix-секунды, например 1790000000
X-SmartAlert-Signature
sha256=<hex>, подпись
X-SmartAlert-Delivery
UUID доставки. Повтор той же доставки несёт тот же id: по нему отбрасывайте дубли
X-SmartAlert-Event
alert.firing, alert.resolved, alert.no_data или test

События

alert.firing
Правило сработало
alert.resolved
Значение вернулось в норму. Тот же alert.id, что у alert.firing
alert.no_data
Серия замолчала: данных нет
test
Кнопка Отправить тест. Поле alert равно null

Тело

json
{
  "version": 1,
  "event": "alert.firing",
  "deliveryId": "6f1c…",
  "sentAt": "2026-10-17T12:00:00Z",
  "alert": {
    "id": "0b8e…",
    "url": "https://…/alerts/0b8e…",
    "severity": "critical",
    "ruleId": "c2d1…",
    "ruleName": "Отток ликвидности",
    "project": { "id": "4a7f…", "name": "Vault" },
    "contract": { "network": "ethereum", "address": "0xabc…", "name": "Pool" },
    "labels": { "token": "USDC" },
    "condition": "gt",
    "value": 1250000,
    "threshold": 1000000,
    "band": null,
    "conditions": null,
    "startedAt": "2026-10-17T11:58:00Z",
    "resolvedAt": null
  }
}
version
Всегда 1
event
То же, что в X-SmartAlert-Event
deliveryId
То же, что в X-SmartAlert-Delivery
sentAt
Время отправки, RFC 3339 UTC
alert
null у test, иначе срабатывание
alert.id
Инцидент: firing и resolved одного инцидента несут один id
alert.url
Страница срабатывания в приложении
alert.severity
critical, warning или info
alert.labels
Серия (значения группировки правила). {}, если группировки нет
alert.condition
Оператор условия правила
alert.value, alert.threshold
Значение и порог
alert.band
{"norm": n, "edge": e} у правила с нормой: норма и пересечённая граница. Тогда threshold равен null
alert.conditions
У правила из двух условий — [{"met": true, "text": "Не было пополнения за 1 ч"}, …]: по элементу на условие, met — выполнено ли, текст на языке канала. У правила из одного условия null
alert.unit
"usd" у правила с порогом в $ (Правила): тогда value и threshold — в $
alert.transfer
У правила с порогом в $ — перевод, давший значение: {"txHash", "blockNumber", "amount", "symbol", "price", "priceSource", "priceAt"}. У других правил null
alert.call
У правила состояния контракта (Контракты) — ответ функции: {"signature", "args", "raw", "previous", "reverted"}. value у bool — 1 или 0, у address — null. У других правил null
alert.startedAt
Начало инцидента
alert.resolvedAt
Конец инцидента у alert.resolved, иначе null
text
Свой текст уведомления правила (Уведомления), null, если его нет

Отсутствующее значение всегда null, поле не пропадает. В версии 1 могут появиться новые поля: игнорируйте незнакомые.

Проверка подписи

Секрет (whsec_…) SmartAlert показывает один раз: при создании канала и при смене секрета. Ключ HMAC — вся строка секрета, вместе с префиксом.

text
signature = hex( HMAC-SHA256( secret, timestamp + "." + rawBody ) )
X-SmartAlert-Signature: sha256=<signature>

rawBody — байты тела как пришли, до разбора JSON. Метка времени входит в подпись, поэтому перехваченный запрос нельзя повторить с новой.

  1. Отклоните запрос, если X-SmartAlert-Timestamp отличается от ваших часов больше чем на 300 секунд.
  2. Посчитайте подпись и сравните с заголовком за постоянное время.
  3. Отбрасывайте дубли по X-SmartAlert-Delivery.

Node

Нужно сырое тело: в Express подключите express.raw, не express.json.

js
import crypto from "node:crypto";
import express from "express";

const SECRET = process.env.SMARTALERT_SECRET; // whsec_…

function verify(secret, headers, rawBody) {
  const ts = headers["x-smartalert-timestamp"];
  const got = headers["x-smartalert-signature"];
  if (!ts || !got || !Number.isFinite(Number(ts))) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const mac = crypto.createHmac("sha256", secret).update(`${ts}.`).update(rawBody).digest("hex");
  const a = Buffer.from(got);
  const b = Buffer.from(`sha256=${mac}`);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();
app.post("/smartalert", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(SECRET, req.headers, req.body)) return res.sendStatus(401);
  const event = JSON.parse(req.body.toString("utf8"));
  // Дубли отбрасывайте по event.deliveryId, обработку делайте асинхронно.
  res.sendStatus(200);
});
app.listen(3000);

Python

python
import hashlib
import hmac
import time

def verify(secret: str, headers, raw_body: bytes) -> bool:
    ts = headers.get("X-SmartAlert-Timestamp", "")
    got = headers.get("X-SmartAlert-Signature", "")
    try:
        if abs(time.time() - int(ts)) > 300:
            return False
    except ValueError:
        return False
    mac = hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256)
    return hmac.compare_digest("sha256=" + mac.hexdigest(), got)

Во Flask raw_body — это request.get_data(), в FastAPI — await request.body(). Берите байты до разбора JSON.

Смена секрета

Кнопка Сменить секрет выдаёт новый секрет. Он действует сразу, перекрытия нет: старый секрет не проходит проверку с первого запроса после смены. Обновите секрет у получателя до смены или сразу после неё.

Ответ и повторы

2xx
Доставлено. Тело ответа не читается дальше 200 символов, они видны в журнале доставок
408, 429, 5xx, таймаут, обрыв
До 5 попыток с паузами 1, 4, 16, 64 и 256 секунд. Retry-After на 429 соблюдается, до 5 минут
404, 410
Вебхук удалён: доставка в статусе «ошибка», канал «отключён», владельцу уходит письмо. Только для боевой доставки, тест канал не отключает
Остальное, включая 3xx
«Ошибка», без повторов

Отвечайте быстро, до 5 секунд, и обрабатывайте асинхронно. Повтор несёт тот же X-SmartAlert-Delivery.

Тест

Отправить тест шлёт событие test с alert: null, подписанное тем же секретом. Ответ 2xx включает канал. По тестовому запросу удобно писать и отлаживать обработчик.

PagerDuty через webhook

Для PagerDuty есть отдельный канал: ключ интеграции, без посредника — см. Уведомления → PagerDuty. Этот рецепт нужен, только если нужен свой формат инцидента.

PagerDuty Events API v2 принимает свой формат, а не этот, поэтому между webhook и PagerDuty ставится функция-посредник (облачная функция, Zapier, n8n). Она проверяет подпись, перекладывает поля и отправляет запрос в API PagerDuty.

dedup_key
alert.id
event_action: trigger или resolve
event: alert.firing или alert.resolved
payload.summary
alert.ruleName и alert.contract.name
payload.severity
alert.severity
links[].href
alert.url

Один и тот же alert.id у alert.firing и alert.resolved позволяет закрыть тот же инцидент в PagerDuty. События alert.no_data и test в его формат не переводите, если они вам не нужны: функция может ответить на них 200 и ничего не делать.

Нашли ошибку или не нашли ответ? Напишите