для разработчиков crm · версия 1
API hoba control: события объекта в вашей CRM
Нарушения на входе, долго открытая дверь, вход вне часов работы — с фото и видео. REST для выборки и вебхуки с подписью, чтобы событие появлялось в CRM сразу.
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 · подключение
Быстрый старт
- 01
Ключ от владельца
Кабинет hoba control → «Интеграции» → «Создать ключ». Владелец выбирает объекты и может указать рядом номер клуба в вашей системе — он придёт в
object.external_id. Ключ показывается один раз. - 02
Ключ в каждом запросе
Заголовок
X-Partner-Key: <ключ>илиAuthorization: Bearer <ключ>. Только HTTPS. - 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
}| Поле | Тип | Что это |
|---|---|---|
id | uuid | Событие. Обновления приходят с тем же id |
version | int | Версия формата, сейчас 1 |
occurred_at | datetime | Когда произошло, ISO 8601 с поясом объекта |
received_at | datetime | Когда дошло до сервера. Позже occurred_at, если устройство было без связи |
updated_at | datetime | Последнее изменение: клип готов, отметка «Проверено», итог по двери, отзыв |
object | object | id и name объекта, external_id — номер у вас (задаёт владелец) |
camera | object | id и name камеры |
type, type_label | string | Код типа и название для показа людям |
severity | string | low, medium или high |
details | object | Зависит от типа, длительности — в секундах |
verified | bool | Команда объекта разобрала событие («Проверено»). Это не «нарушение подтверждено» |
revoked_at | datetime | null | Отозвано: команда объекта отметила событие ложным срабатыванием |
media | object | Ссылки на фото и клип, срок их жизни, готовность клипа и доступность устройства |
dashboard_url | string | Карточка события в кабинете hoba control (нужен вход) |
sandbox | bool | true — тестовое событие |
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 секунд, поддерживает Range | video/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-Delivery | id доставки — одинаковый во всех повторах |
X-Hoba-Timestamp | Unix-время этой попытки, секунды |
X-Hoba-Signature | sha256=<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 — поможем с ключом и проверкой вебхука.
