Webhooks

Webhooks let you receive real-time HTTP notifications whenever an event occurs in your SmartElektroHub account — a stream goes live, a subscriber joins, a record is published, and more. Your endpoint receives a POST request with a JSON payload.

SmartElektroHub retries failed webhook deliveries up to 5 times with exponential back-off (1 min → 5 min → 30 min → 2 h → 8 h). After all retries fail, the event is marked as undelivered and visible in the Dashboard.

Creating a webhook

Webhooks can be created from the Dashboard under Settings → Webhooks, or via the API:

curl -X POST https://api.smart-elektro.ru/v2/webhooks \
  -H "Authorization: Bearer eh_sk_••••••••••••••••" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourserver.example/webhooks/sehub",
    "events": ["device.online", "device.offline", "report.generated"],
    "description": "Production webhook"
  }'

Response

{
  "id": "wh_01jz4nmrw8bq5vkcpx2t3ea",
  "object": "webhook",
  "url": "https://yourserver.example/webhooks/sehub",
  "events": ["device.online", "device.offline", "report.generated"],
  "signingSecret": "whsec_9f2k3hx7p4qm1rlnbva8czdt",
  "status": "active",
  "createdAt": "2026-03-26T14:32:11Z"
}
Save the signingSecret immediately — it is shown only once and cannot be retrieved later. Use it to verify incoming payloads.

Event types

EventTrigger
device.onlineMQTT connection received; stream is now live
device.offlineMQTT connection dropped; transmission finished
device.errorIngestion error detected (format mismatch, bitrate spike, etc.)
subscriber.connectedA subscriber connected to a stream
subscriber.disconnectedA subscriber disconnected from a stream
report.generatedA report record was published and available in the feed
report.deletedA published record was deleted
webhook.testSent when you click Send test event in the Dashboard

Payload structure

All events share the same envelope. The data field contains the affected object in its current state.

POST https://yourserver.example/webhooks/sehub
Content-Type: application/json
SmartElektroHub-Signature: t=1711460331,v1=3c8a2f…
SmartElektroHub-Event: device.online

{
  "id": "evt_01kp2xcm9fjr7tlqbu3nz8w",
  "object": "event",
  "type": "device.online",
  "created": 1711460331,
  "livemode": true,
  "data": {
    "object": {
      "id": "stm_01hx83nqp5fk2rjavt9c6e",
      "title": "Evening Show",
      "status": "live",
      "subscriberCount": 0,
      "startedAt": "2026-03-26T14:38:51Z"
    }
  }
}

Signature verification

Every webhook request includes a SmartElektroHub-Signature header. Verify it using HMAC-SHA256 to ensure the request is genuinely from SmartElektroHub.

Verification algorithm

  1. Split the header on , to get the t (timestamp) and v1 (signature) values.
  2. Construct the signed payload: {t}.{raw_request_body}
  3. Compute HMAC-SHA256 of the signed payload using the signingSecret as key.
  4. Compare your result against the v1 value (constant-time comparison).
  5. Reject requests where t is more than 300 seconds old.

Node.js example

import crypto from 'node:crypto';

const SIGNING_SECRET = process.env.EH_WEBHOOK_SECRET;
const TOLERANCE_SECONDS = 300;

function verifyWebhook(rawBody, signatureHeader) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map(p => p.split('='))
  );
  const timestamp = parseInt(parts.t, 10);

  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) {
    throw new Error('Webhook timestamp out of tolerance');
  }

  const signedPayload = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac('sha256', SIGNING_SECRET)
    .update(signedPayload)
    .digest('hex');

  const received = parts.v1;
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
    throw new Error('Webhook signature mismatch');
  }
}

// Express usage
app.post('/webhooks/sehub', express.raw({ type: 'application/json' }), (req, res) => {
  verifyWebhook(req.body.toString(), req.headers['sehub-signature']);
  const event = JSON.parse(req.body);
  // handle event…
  res.sendStatus(200);
});

Python example

import hashlib
import hmac
import time

SIGNING_SECRET = os.environ["EH_WEBHOOK_SECRET"]
TOLERANCE = 300

def verify_webhook(raw_body: bytes, signature_header: str):
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    ts = int(parts["t"])

    if abs(time.time() - ts) > TOLERANCE:
        raise ValueError("Webhook timestamp out of tolerance")

    signed = f"{ts}.{raw_body.decode()}".encode()
    expected = hmac.new(SIGNING_SECRET.encode(), signed, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, parts["v1"]):
        raise ValueError("Webhook signature mismatch")

Вебхуки

Вебхуки позволяют получать HTTP-уведомления в реальном времени при наступлении событий в вашем аккаунте SmartElektroHub — когда поток выходит в эфир, подписчик подключается, запись публикуется и так далее. На ваш эндпоинт поступает POST-запрос с JSON-телом.

SmartElektroHub повторяет неудавшиеся доставки до 5 раз с экспоненциальной задержкой (1 мин → 5 мин → 30 мин → 2 ч → 8 ч). После исчерпания попыток событие помечается как недоставленное и отображается в Dashboard.

Создание вебхука

Вебхуки создаются в разделе Dashboard → Settings → Webhooks или через API:

curl -X POST https://api.smart-elektro.ru/v2/webhooks \
  -H "Authorization: Bearer eh_sk_••••••••••••••••" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourserver.example/webhooks/sehub",
    "events": ["device.online", "device.offline", "report.generated"],
    "description": "Продакшн-вебхук"
  }'

Ответ

{
  "id": "wh_01jz4nmrw8bq5vkcpx2t3ea",
  "object": "webhook",
  "url": "https://yourserver.example/webhooks/sehub",
  "events": ["device.online", "device.offline", "report.generated"],
  "signingSecret": "whsec_9f2k3hx7p4qm1rlnbva8czdt",
  "status": "active",
  "createdAt": "2026-03-26T14:32:11Z"
}
Сохраните signingSecret сразу — он показывается только один раз и не может быть получен позже. Используйте его для верификации входящих запросов.

Типы событий

СобытиеКогда отправляется
device.onlineПолучено MQTT-соединение; поток вышел в эфир
device.offlineMQTT-соединение разорвано; мониторинг завершено
device.errorОшибка ингеста (несоответствие протокола, скачок битрейта и т.п.)
subscriber.connectedПодписчик подключился к потоку
subscriber.disconnectedПодписчик отключился от потока
report.generatedЗапись отчёта опубликован и доступен в фиде
report.deletedОпубликованный запись удалён
webhook.testТестовое событие из раздела Dashboard

Структура payload

Все события имеют единый формат. Поле data содержит затронутый объект в его текущем состоянии.

POST https://yourserver.example/webhooks/sehub
Content-Type: application/json
SmartElektroHub-Signature: t=1711460331,v1=3c8a2f…
SmartElektroHub-Event: device.online

{
  "id": "evt_01kp2xcm9fjr7tlqbu3nz8w",
  "object": "event",
  "type": "device.online",
  "created": 1711460331,
  "livemode": true,
  "data": {
    "object": {
      "id": "stm_01hx83nqp5fk2rjavt9c6e",
      "title": "Вечернее шоу",
      "status": "live",
      "subscriberCount": 0,
      "startedAt": "2026-03-26T14:38:51Z"
    }
  }
}

Верификация подписи

Каждый запрос вебхука содержит заголовок SmartElektroHub-Signature. Проверяйте его через HMAC-SHA256, чтобы убедиться, что запрос действительно пришёл от SmartElektroHub.

Алгоритм проверки

  1. Разбейте заголовок по символу ,, получив значения t (временная метка) и v1 (подпись).
  2. Сформируйте подписанную строку: {t}.{тело_запроса}
  3. Вычислите HMAC-SHA256 этой строки, используя signingSecret как ключ.
  4. Сравните результат с v1 (сравнение должно быть защищено от тайминг-атак).
  5. Отклоняйте запросы, где t устарел более чем на 300 секунд.

Пример на Node.js

import crypto from 'node:crypto';

const SIGNING_SECRET = process.env.EH_WEBHOOK_SECRET;
const TOLERANCE_SECONDS = 300;

function verifyWebhook(rawBody, signatureHeader) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map(p => p.split('='))
  );
  const timestamp = parseInt(parts.t, 10);

  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) {
    throw new Error('Временная метка вебхука вне допустимого диапазона');
  }

  const signedPayload = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac('sha256', SIGNING_SECRET)
    .update(signedPayload)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) {
    throw new Error('Подпись вебхука не совпадает');
  }
}

Пример на Python

import hashlib, hmac, os, time

SIGNING_SECRET = os.environ["EH_WEBHOOK_SECRET"]
TOLERANCE = 300

def verify_webhook(raw_body: bytes, signature_header: str):
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    ts = int(parts["t"])

    if abs(time.time() - ts) > TOLERANCE:
        raise ValueError("Временная метка вебхука вне допустимого диапазона")

    signed = f"{ts}.{raw_body.decode()}".encode()
    expected = hmac.new(SIGNING_SECRET.encode(), signed, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, parts["v1"]):
        raise ValueError("Подпись вебхука не совпадает")