Каталог событий
Все события, которые порождает панель, с областью доступа (scope), нужной получателю, и полями, которые они несут. Подписчики-вебхуки, дополнения, чаты Telegram и адреса электронной почты выбирают из этого списка. Как настроить подписчика, см. Вебхуки и события; как события ставятся в очередь и доставляются — Шина событий.
Тот же каталог отдаёт сама панель по адресу GET /api/v1/events; читать его может любой API-токен.
Конверт
Каждое событие приходит в вебхук как POST с телом JSON:
{
"v": 1,
"id": 4182,
"event": "user.quota_warning",
"time": 1760180000,
"data": {
"userId": 57,
"name": "alice",
"adminId": 3,
"usedPercent": 81,
"threshold": 80,
"used": 86973087744,
"volume": 107374182400
},
"recipients": [3]
}| Поле | |
|---|---|
v | версия конверта, 1 |
id | идентификатор события: одинаков для всех подписчиков и при каждом повторе |
event | имя события |
time | когда событие возникло, в секундах Unix |
data | данные: поля, перечисленные для события ниже |
recipients | идентификаторы администраторов, которых касается событие, если касается; иначе поле отсутствует |
Время в данных указывается в секундах Unix, объём трафика — в байтах. Данные никогда не содержат пароль, ключ или ссылку подписки; если они нужны, читайте их через API с токеном.
Заголовки и подпись
| Заголовок | |
|---|---|
Content-Type | application/json |
User-Agent | nexora-panel |
X-Nexora-Event | имя события |
X-Nexora-Delivery | идентификатор доставки: одинаков при каждом повторе и при Отправить снова |
X-Nexora-Signature | t=<unix seconds>,v1=<hex>, есть, если у подписчика задан секрет |
v1 — это HMAC-SHA256 в шестнадцатеричном виде от метки времени, точки и исходного тела, с ключом — секретом подписи подписчика:
v1 = hex( HMAC-SHA256( secret, "<t>" + "." + <raw body bytes> ) )Получатель должен:
- Прочитать исходное тело до разбора; заново сериализованное тело не совпадёт.
- Заново вычислить
v1своим секретом и сравнить за постоянное время. - Отклонять запрос, чей
tотличается от его собственных часов больше чем на пять минут, чтобы перехваченную доставку нельзя было повторить позже. - Быстро отвечать
2xx. Любой другой ответ или отсутствие ответа в течение десяти секунд считается ошибкой, и доставка повторяется.
На Python:
import hashlib, hmac, time
def verify(secret: bytes, header: str, body: bytes, max_skew: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
t, v1 = parts.get("t"), parts.get("v1")
if not t or not v1 or abs(time.time() - int(t)) > max_skew:
return False
mac = hmac.new(secret, t.encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, v1)Чтобы обрабатывать каждое событие один раз, храните уже обработанные id. Восстановление из резервной копии возвращает идентификаторы копии, поэтому получатель, который помнит id между восстановлениями, должен начать сначала, получив panel.restore_applied.
Кто что получает
Область доступа. Каждое событие называет область, нужную получателю. Дополнение может запросить событие, только если и его токен имеет эту область, и экран согласия показывает обе (область :write включает соответствующую :read). Чат Telegram или адрес почты получает событие, только если роль его учётной записи имеет эту область. Вебхук, который главный администратор добавил вручную, получает все события, названные в его фильтре.
Семейства. Часть до точки — семейство, и оно определяет маршрутизацию:
| Семейство | О чём | Называет учётную запись |
|---|---|---|
user.* | учётная запись клиента | да: adminId — реселлер-владелец (0 для панели) |
node.* | узел | нет |
admin.* | учётная запись оператора | да: adminId — эта учётная запись |
panel.* | сама панель | нет |
Подписчик, у которого в Только события о выбрана одна учётная запись, а также любой чат или адрес реселлера получают только события, называющие эту учётную запись. Поэтому события node.* и panel.* никогда не доходят до реселлера.
Массовая операция порождает одно событие. Массовая операция на странице пользователей порождает один user.bulk, а генерация пачки учётных записей — один user.generated, сколько бы учётных записей они ни затронули.
user.*
Каждое событие user.* несёт эти три поля и поля из своей строки:
| Поле | Тип | |
|---|---|---|
userId | integer | идентификатор учётной записи |
name | string | имя учётной записи |
adminId | integer | реселлер-владелец; 0 для панели |
| Событие | Область | Когда возникает | Доп. поля |
|---|---|---|---|
user.created | users:read | Учётная запись создана. | |
user.deleted | users:read | Учётная запись удалена. | |
user.enabled | users:read | Панель снова включила учётную запись, например после продления или сброса расхода. | |
user.disabled | users:read | Панель выключила учётную запись. reason — volume, expiry, resale-volume или resale-expiry. | reason |
user.quota_reached | users:read | Случай user.disabled по трафику: учётная запись израсходовала оплаченное. | reason |
user.expired | users:read | Случай user.disabled по дате: время учётной записи вышло. | reason |
user.quota_warning | users:read | Учётная запись перешла порог Израсходовано трафика (по умолчанию 80 %). Один раз на лимит и порог. | usedPercent, threshold, used, volume |
user.expiring | users:read | Учётная запись вошла в одно из окон Дней до истечения (по умолчанию 7 и 1). Один раз на окно. | expiry, daysLeft, threshold |
user.renewed | users:read | Учётная запись продлена. Несёт новую дату и трафик. | expiry, volume |
user.activated | users:read | Тариф, который начинается с первого подключения, начался; дата окончания теперь зафиксирована. | expiry |
user.first_fetch | users:read | Клиент впервые забрал конфигурации учётной записи. client называет приложение. | client |
user.device_limit_reached | users:read | Устройству отказано в подписке, потому что лимит устройств учётной записи заполнен. Не чаще раза в час на устройство. | hwid, limit |
Два события user.* несут свои поля вместо трёх перечисленных:
| Событие | Область | Когда возникает | Поля |
|---|---|---|---|
user.bulk | users:read | Завершилась одна массовая операция. op — операция; ids — учётные записи, до которых она дошла; adminId — реселлер, если её выполнил он. | op, matched, affected, blocked, failed, actor, adminId, ids |
user.generated | users:read | Пачка учётных записей сгенерирована по шаблону имени. | pattern, count, skipped, planId, actor, adminId |
Пороги задаются на странице Вебхуки и события.
node.*
Большинство событий node.* несут идентификатор и имя узла:
| Событие | Область | Когда возникает | Поля |
|---|---|---|---|
node.connected | nodes:read | Узел начал отвечать панели. warnings перечисляет то, что он не запустил. | nodeId, name, status, message, warnings |
node.disconnected | nodes:read | Узел перестал отвечать. message — ошибка. | nodeId, name, status, message, warnings |
node.limit_reached | nodes:read | Узел достиг своего периодического лимита трафика и выключен. period — цикл. | nodeId, name, used, limit, period |
node.disk_high | nodes:read | Корневая файловая система узла перешла порог Заполнение диска (по умолчанию 90 %). Один раз на переход. | поля хоста |
node.disk_recovered | nodes:read | Значение опустилось ниже порога не меньше чем на пять пунктов. | поля хоста |
node.memory_high | nodes:read | Память узла перешла порог Использование памяти (по умолчанию 90 %). Один раз на переход. | поля хоста |
node.memory_recovered | nodes:read | Значение опустилось ниже порога не меньше чем на пять пунктов. | поля хоста |
node.rejections_high | nodes:read | За час узел отклонил через один блокирующий аутбаунд больше соединений, чем Отклонённых соединений в час (по умолчанию 100). outbound — тег этого аутбаунда; window — 1h. Один раз на переход. | nodeId, name, outbound, count, threshold, window |
Поля хоста: nodeId, name, resource (disk или memory), usedPercent, threshold, used и total (байты). Диск и память считываются каждые пять минут.
admin.*
| Событие | Область | Когда возникает | Поля |
|---|---|---|---|
admin.login | admins:write | Оператор вошёл. | adminId, username, ip |
admin.login_failed | admins:write | Вход отклонён. username — то, что было введено; stage равен mfa при неверном двухфакторном коде. | username, ip, stage |
admin.login_new_ip | admins:write | Оператор вошёл с адреса, отличного от адреса предыдущего входа. | adminId, username, ip, previousIp |
admin.2fa_changed | admins:write | Оператор включил или выключил двухфакторную аутентификацию. | adminId, username, enabled |
admin.owner_changed | admins:write | Панель передана другому главному администратору. previous называет учётную запись, которая её отдала. | adminId, username, previous |
admin.resale_cap_reached | admins:write | Реселлер израсходовал свой лимит или его учётная запись истекла; его пользователи отключились вместе с ним. reason — volume или expiry. | adminId, username, reason, used, volume |
panel.*
| Событие | Область | Когда возникает | Поля |
|---|---|---|---|
panel.started | stats:read | Панель запустилась. restored говорит, что по пути было применено восстановление. | version, listen, restored |
panel.stopping | stats:read | Панель штатно завершает работу, в том числе при перезапуске. panel.started без предшествующего panel.stopping означает, что панель остановилась неожиданно. | version |
panel.crashed | stats:read | При ответе на запрос перехвачена внутренняя ошибка; панель продолжает работать. Не чаще раза в пять минут на маршрут и сообщение. | method, path, error, version |
panel.setup_completed | settings:read | Мастер настройки завершён, главный администратор создан. | username, restart |
panel.settings_changed | settings:read | Изменена общая настройка панели (адрес, TLS, базовый путь и т. п.). Значение никогда не передаётся. | setting, actor |
panel.restore_applied | users:read | Поверх этой панели восстановлена резервная копия. | superseded, tables, rows |
panel.backup_done | backup:read | Резервное копирование по расписанию записало архив. | name, size |
panel.backup_failed | backup:read | Резервное копирование по расписанию не удалось; новая попытка — в течение часа. | error |
panel.backup_upload_failed | backup:read | Архив не дошёл до места хранения резервных копий. Он остаётся на сервере панели. | destination, name, error |
panel.cert_renewed | certificates:read | Сертификат, которым управляет панель, продлён. | поля сертификата |
panel.cert_renew_failed | certificates:read | Продление не удалось; новая попытка — каждые шесть часов. | поля сертификата, error |
panel.cert_expiring | certificates:read | Загруженный вами сертификат подходит к концу срока; панель не может его продлить. Один раз на сертификат. | поля сертификата |
panel.ruleset_refresh_failed | pool:read | Ежечасное обновление наборов правил не удалось. | error |
panel.update_available | stats:read | Есть более новый выпуск панели. version — новый, current — работающий. Один раз на выпуск. | version, current, url |
panel.license_changed | license:write | Изменилось состояние лицензии: лицензирована, истекла или бесплатна, либо новые лимиты. | licensed, reason, previousReason, licenseId, issuedTo, expiresAt, limits |
panel.license_expiring | license:write | Лицензия заканчивается через 30, 14, 7 или 1 день. Один раз на порог. Льготного периода после даты нет. | licenseId, issuedTo, expiresAt, daysLeft, threshold |
panel.license_unverified | license:write | Панель несколько дней не может подтвердить свою лицензию. deadline — когда она перейдёт на бесплатный уровень, если так продолжится. | licenseId, verifiedAt, deadline, daysLeft |
panel.ip_banned | security:read | Защита входа заблокировала адрес. | ip, until, reason |
panel.addon_registered | tokens:write | Дополнение зарегистрировано. | поля дополнения |
panel.addon_removed | tokens:write | Дополнение удалено вместе с его токеном и вебхуком. Само дополнение получает это событие первым, каким бы ни был его фильтр. | поля дополнения |
panel.addon_unhealthy | tokens:write | Путь проверки состояния дополнения дважды подряд не ответил. Один раз на переход. | поля дополнения, error |
panel.addon_recovered | tokens:write | Неисправное дополнение снова ответило на путь проверки состояния. | поля дополнения |
panel.addon_update_waiting | tokens:write | Новая версия подписанного дополнения просит больше, чем у него есть, и ждёт одобрения. | поля дополнения, version |
panel.ping | любая | Тестовое событие, которое Отправить тестовое событие шлёт одному подписчику. | subscriberId, name, by |
Поля сертификата: kind (node или panel), certificateId, nodeId, name и notAfter. Поля дополнения: addonId, slug, name и signed.
Что события лицензии значат для вашей панели, см. Лицензия; о дополнениях — Страница дополнений.
Связанные страницы
- Вебхуки и события: подписчики, повторы и пороги.
- Шина событий: как шина ставит события в очередь и доставляет их.
- Telegram и Почта: события для людей.
- Справочник API: API, из которого получатель читает остальное.
