для разработчиков crm · версия 1

API hoba control: события объекта в вашей CRM

Нарушения на входе, долго открытая дверь, вход вне часов работы — с фото и видео. REST для выборки и вебхуки с подписью, чтобы событие появлялось в CRM сразу.

База: https://crm.hoba-control.ru/v1Формат: JSON, UTF-8
curl https://crm.hoba-control.ru/v1/events?limit=1 \
  -H "X-Partner-Key: hk_live_…"

{
  "data": [{
    "id": "5b1f0c2e-…",
    "type": "tailgating",
    "type_label": "Проход по одному пропуску",
    "occurred_at": "2026-10-08T18:45:12+03:00",
    "object": { "external_id": "club_117", … },
    "media": { "photo_url": "…", "video_url": "…" }
  }],
  "next_cursor": "eyJtIjoi…",
  "has_more": false
}

Владелец объекта сам решает, что увидит ваша система: он выпускает ключ в своём кабинете и отмечает объекты. Доступ выдаёт владелец объекта: он создаёт ключ в кабинете и сам выбирает, какие объекты открыть.

01 · подключение

Быстрый старт

  1. 01

    Ключ от владельца

    Кабинет hoba control → «Интеграции» → «Создать ключ». Владелец выбирает объекты и может указать рядом номер клуба в вашей системе — он придёт в object.external_id. Ключ показывается один раз.

  2. 02

    Ключ в каждом запросе

    Заголовок X-Partner-Key: <ключ> или Authorization: Bearer <ключ>. Только HTTPS.

  3. 03

    События — опросом или вебхуком

    GET /v1/events с курсором либо адрес вебхука: его задаёт владелец в кабинете или вы сами через POST /v1/partners/webhook.

Доступ по ключу

Ключ видит только открытые ему объекты. Отзыв ключа действует сразу — вместе со всеми выданными ссылками на медиа.

Вебхуки с подписью

HMAC-SHA256 на каждую попытку, повторы около 7 часов, журнал доставок через API.

Курсор без потерь

Курсор идёт по времени приёма: события, которые устройство досылает после обрыва связи, не теряются.

Медиа по ссылке

Фото и клип открываются по ссылке, которая живёт час. Каждый просмотр записывается в журнал владельца.

02 · формат

Событие

Один и тот же объект приходит в ответе GET /v1/events, GET /v1/events/{id} и в теле вебхука. Храните события по id: повтор не создаёт дубль, а версию с бо́льшим updated_at применяйте поверх старой.

{
  "id": "5b1f0c2e-8a47-4d1e-9c3a-2f6d8e7b4a10",
  "version": 1,
  "occurred_at": "2026-10-08T18:45:12+03:00",
  "received_at": "2026-10-08T18:45:13+03:00",
  "updated_at": "2026-10-08T18:45:28+03:00",
  "object": { "id": "3d7e2a91-6c0b-4f5e-8a12-9b4c7d1e0f23", "name": "Зал на Ленина 42", "external_id": "club_117" },
  "camera": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "name": "Вход" },
  "type": "tailgating",
  "type_label": "Проход по одному пропуску",
  "severity": "high",
  "details": { "persons_count": 2, "door_open_duration_sec": 8 },
  "verified": false,
  "revoked_at": null,
  "media": {
    "photo_url": "https://crm.hoba-control.ru/v1/media/5b1f0c2e-…/photo?token=…",
    "video_url": "https://crm.hoba-control.ru/v1/media/5b1f0c2e-…/clip?token=…",
    "video_ready": true,
    "expires_at": "2026-10-08T19:45:28+03:00",
    "available": true
  },
  "dashboard_url": "https://crm.hoba-control.ru/alerts/5b1f0c2e-8a47-4d1e-9c3a-2f6d8e7b4a10",
  "sandbox": false
}
ПолеТипЧто это
iduuidСобытие. Обновления приходят с тем же id
versionintВерсия формата, сейчас 1
occurred_atdatetimeКогда произошло, ISO 8601 с поясом объекта
received_atdatetimeКогда дошло до сервера. Позже occurred_at, если устройство было без связи
updated_atdatetimeПоследнее изменение: клип готов, отметка «Проверено», итог по двери, отзыв
objectobjectid и name объекта, external_id — номер у вас (задаёт владелец)
cameraobjectid и name камеры
type, type_labelstringКод типа и название для показа людям
severitystringlow, medium или high
detailsobjectЗависит от типа, длительности — в секундах
verifiedboolКоманда объекта разобрала событие («Проверено»). Это не «нарушение подтверждено»
revoked_atdatetime | nullОтозвано: команда объекта отметила событие ложным срабатыванием
mediaobjectСсылки на фото и клип, срок их жизни, готовность клипа и доступность устройства
dashboard_urlstringКарточка события в кабинете hoba control (нужен вход)
sandboxbooltrue — тестовое событие

03 · каталог

Типы событий

Актуальный список — GET /v1/event-types. Зарезервированные коды можно завести у себя заранее: когда сценарий заработает, события придут под этим кодом.

typeНазваниеdetailsСтатус
tailgatingПроход по одному пропускуpersons_count, door_open_duration_secРаботает
door_open_longДолго открытая дверьduration_secРаботает
unauthorizedВход вне часов работыpersons_countРаботает
animalЖивотное в кадреanimal_classВ разработке
visitПроходdirection, persons_countЗарезервирован
zone_overcapacityЛюдей больше, чем записаноcounted, expectedЗарезервирован
person_idleЧеловек без движенияidle_duration_secЗарезервирован
abandoned_objectОставленный предметobject_class, idle_duration_secЗарезервирован

Часы работы для unauthorized владелец задаёт в кабинете hoba control — это не расписание занятий в вашей системе.

04 · справочник

Методы

МетодЧто делаетОтвет
GET /v1/event-typesКаталог типов событий{data: [{type, type_label, status, severity, details}]}
GET /v1/objectsОбъекты, открытые ключу. limit, cursor{data: [{id, name, external_id, timezone, cameras}], next_cursor, has_more}
GET /v1/eventsСобытия. since или updated_since, type, object_id, object_external_id, limit ≤ 100 (по умолчанию 50), cursor{data, next_cursor, has_more}
GET /v1/events/{id}Событие целиком, со свежими ссылками на медиаСобытие
GET /v1/media/{id}/photo?token=…Фотоimage/jpeg
GET /v1/media/{id}/clip?token=…Клип ±10 секунд, поддерживает Rangevideo/mp4
POST /v1/partners/webhookЗадать адрес вебхука {url}. Каждый вызов выдаёт новый секрет{webhook_url, webhook_secret}
DELETE /v1/partners/webhookВыключить вебхук204
POST /v1/partners/webhook/testОтправить тестовое событие (sandbox: true){delivered, response_code}
GET /v1/deliveriesЖурнал доставок, новые сверху. status (pending, failed, delivered, dead), since, limit, cursor{data, next_cursor, has_more}

Время в параметрах — ISO 8601. Используйте Z или закодируйте плюс в поясе как %2B: в адресе + превращается в пробел.

05 · синхронизация

Как забирать события

Надёжнее всего опрос по курсору. Начните с updated_since: в одном потоке придут и новые события, и изменения старых — отзыв, «Проверено», готовый клип. Сохраняйте next_cursor после каждого ответа и продолжайте с него.

# 1. Первый запрос — всё, что изменилось с нужного момента
GET /v1/events?updated_since=2026-10-28T00:00:00Z&limit=100

# 2. Дальше — только по курсору, раз в 30–60 секунд
GET /v1/events?cursor=<next_cursor из прошлого ответа>&limit=100

# has_more: true — сразу запросите следующую страницу.
# Пустой data — новых изменений нет, курсор остаётся прежним.
  • Нужны только новые события?

    Начните с since — поток по времени приёма, без изменений старых.

  • Последние 5 секунд в ответ не попадают

    Так курсор не перешагнёт событие, которое сервер ещё записывает. Оно придёт следующим запросом.

  • Порядок вебхуков не гарантирован

    Ориентируйтесь на occurred_at и updated_at, а не на порядок прихода.

06 · персональные данные

Фото и видео

Медиа хранятся на устройстве в объекте и отдаются транзитом — копий на нашем сервере нет. Ссылки из события живут час (media.expires_at); свежие — в GET /v1/events/{id}. Ключ в заголовке для ссылки не нужен, её можно открыть в браузере менеджера.

Клип собирается около 10 секунд после события: пока video_ready равно false, ссылка отвечает 503, а когда клип готов, придёт event.updated.

КодКогда
200Фото или клип целиком
206Часть клипа — ответ на запрос с Range
402Подписка владельца объекта не оплачена
403Ссылка истекла, ключ отозван или объект закрыт для ключа
404Медиа нет: клип не записался или старше 45 дней
503Устройство не на связи или клип ещё собирается — повторить после Retry-After
504Устройство не ответило

07 · push

Вебхуки

POST с телом-событием на ваш адрес. Адрес — только HTTPS на публичном хосте; перенаправления не выполняются. Успех — любой ответ 2xx за 10 секунд.

X-Hoba-EventКогда приходит
event.createdСобытие дошло до сервера. После обрыва связи события приходят пачкой
event.updatedКлип готов; дверь закрылась — итоговая длительность; изменилась отметка «Проверено»; с события сняли отметку «ложное»
event.revokedСобытие отмечено ложным срабатыванием — уберите или пометьте его у себя
ЗаголовокЗначение
X-Hoba-Deliveryid доставки — одинаковый во всех повторах
X-Hoba-TimestampUnix-время этой попытки, секунды
X-Hoba-Signaturesha256=<hex> от HMAC-SHA256(секрет, timestamp + "." + сырое тело)

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

Считайте подпись от сырого тела запроса, до разбора JSON. Запросы с временем, отличающимся от ваших часов больше чем на 5 минут, отбрасывайте.

import hashlib, hmac, time

def verify(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    mac = hmac.new(secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256)
    return hmac.compare_digest("sha256=" + mac.hexdigest(), signature)
const crypto = require('node:crypto');

function verify(secret, timestamp, rawBody, signature) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = 'sha256=' + crypto.createHmac('sha256', secret)
    .update(timestamp + '.').update(rawBody).digest('hex');
  return expected.length === signature.length
    && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Повторы

Если ответ не 2xx или его нет, повторяем: сразу → через 30 с → 2 мин → 10 мин → 1 ч → 6 ч. После шестой неудачи доставка получает статус dead и видна в GET /v1/deliveries; само событие можно забрать через GET /v1/events. Ответ 410 прекращает повторы сразу.

08 · справочник

Ошибки и лимиты

КодКогда
400Неверный курсор или параметр
401Нет ключа, ключ неверный или отозван
402Подписка владельца не оплачена. События при этом записываются, а вебхуки ждут — после оплаты всё придёт
403Объект не открыт для ключа
404Событие не найдено или старше 90 дней
422Неверное тело запроса, например адрес вебхука не HTTPS
429Больше 120 запросов в минуту на ключ — подождите Retry-After
Нужен тестовый доступ?

Напишите на info@hoba-control.ru — поможем с ключом и проверкой вебхука.