API

API: данные webhook

JSON, который Tenium отправляет на webhook, когда монитор не работает, восстанавливается или предупреждает о сроке действия, плюс заголовки и подпись.

Контакты webhook получают POST с Content-Type: application/json на каждое событие. Тело запроса — то же сообщение, которое Tenium формирует для e-mail и чатов.

События

Поле event (его также передают в заголовке X-Tenium-Event) принимает одно из таких значений:

  • down: монитор подтверждённо недоступен
  • blocked: чекер останавливает WAF или защита от ботов; сам сайт, скорее всего, работает
  • up: монитор восстановился (отправляется только контактам, получившим оповещение о сбое)
  • degraded: время ответа превышает порог или изменилось содержимое страницы
  • heartbeat_missed: heartbeat-монитор не прислал пинг вовремя
  • ssl_expiry: срок действия сертификата SSL истекает или уже истёк
  • domain_expiry: срок действия домена истекает или уже истёк
  • test: тестовое оповещение со страницы контакта

Данные события

{
  "version": 1,
  "event": "down",
  "kind": "down",
  "title": "DOWN",
  "summary": "Client A shop · 503 Service Unavailable · confirmed from Frankfurt (FRA), London (LON), New York (NYC)",
  "color": "red",
  "sentAt": "2026-01-19T09:39:14.000Z",
  "region": { "code": "fra", "name": "Frankfurt" },
  "account": { "id": "0f8e...", "name": "Your Studio", "brandName": null, "brandEmailFrom": null, "brandEmailVerifiedAt": null, "timezone": "Europe/Tallinn" },
  "client": { "id": "9a8b...", "name": "Client A" },
  "monitor": {
    "id": "a3f1...",
    "name": "Client A shop",
    "type": "http",
    "target": "https://shop.clienta.com",
    "url": "https://app.tenium.app/app/monitors/a3f1..."
  },
  "incident": {
    "id": "e5b4...",
    "severity": "down",
    "cause": "503 Service Unavailable",
    "statusCode": 503,
    "startedAt": "2026-01-19T09:39:12.000Z",
    "resolvedAt": null,
    "durationSec": 2,
    "confirmedRegions": ["fra", "lon", "nyc"],
    "acknowledgedAt": null,
    "upstreamProvider": null,
    "lastResponse": null,
    "url": "https://app.tenium.app/app/incidents/e5b4...",
    "ackUrl": "https://app.tenium.app/app/incidents/e5b4...?action=ack"
  },
  "expiry": null,
  "nextStep": "Check the server / hosting panel. Acknowledge so the team knows you are on it."
}

kind — внутренний тип оповещения; он совпадает с event, кроме blocked, которое приходит как kind: "down". Для up заполнены resolvedAt и durationSec. Для событий о сроке действия incident равен null, а вместо него передаётся expiry: { "what": "SSL certificate", "expiresAt": "...", "daysLeft": 7, "issuer": "..." }. Для мониторов вне пространства клиента client равен null.

Заголовки

Каждый запрос содержит:

  • X-Tenium-Event: название события, например down
  • X-Tenium-Delivery: уникальный идентификатор этой доставки
  • X-Tenium-Timestamp: время sentAt из данных события
  • X-Tenium-Signature: есть, если для контакта задан секрет подписи

Подпись

X-Tenium-Signature: t=1737279554,v1=5f3a...

t — время Unix в секундах, когда запрос подписан. v1 — HMAC-SHA256 от "{t}.{rawBody}" с секретом подписи контакта (его показывают при создании контакта). Отклоняйте запросы старше 5 минут.

Пример на Node:

import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Повторные попытки

Отвечайте любым кодом 2xx в течение 10 секунд. Сетевые ошибки, таймауты и ответы 5xx и 429 повторяются: до 3 попыток всего с паузой 30 секунд. Остальные ответы 4xx не повторяются.