HTTP API и вебхуки

Всё, что делает админка, доступно и через HTTP API: биллинг, мониторинг или свой скрипт управляют панелью так же, как администратор. В обратную сторону работают вебхуки — панель сама сообщает внешней системе о событиях. Биллингу обычно нужно и то, и другое. Готовый модуль для WHMCS — Модуль WHMCS.

API-ключи

API-ключ — пароль для программы. С ним внешняя система работает от своего имени, без логина сотрудника и кода 2FA. Ключи выпускает только администратор: Доступ → API-ключи → Новый ключ, название и скоуп:

  • read — только чтение: ноды, тарифы, каталог. Изменить через него ничего нельзя. Игровых серверов такой ключ не видит — у ключа нет владельца, а серверы привязаны к владельцам.
  • admin — полный доступ ко всему API, как у администратора. Нужен биллингу. Выдавайте его только системам, которым доверяете так же, как администратору.

Ключ вида gmk_… показывается один раз — панель хранит только его хэш. Потеряли — отзовите и выпустите новый.

Ключ передаётся в заголовке каждого запроса:

curl -H "Authorization: Bearer gmk_…" https://panel.example.com/api/nodes

Советы:

  • Выпускайте отдельный ключ для каждой системы. Тогда в списке видно, когда каждый ключ использовался, в журнале аудита действия записаны под его именем, а отзыв одного ключа не ломает остальные интеграции.
  • Храните ключ в настройках или переменных окружения системы, не в коде и не в репозитории.
  • Ключ утёк — Отозвать: он перестаёт работать сразу. Отозванный ключ остаётся в списке, чтобы не терялась история в аудите.
  • На каждый ключ действует лимит: по умолчанию 20 запросов в секунду со всплеском до 200. При превышении панель отвечает 429. Лимит меняется в Настройках.

Описание методов

Полное описание API — в самой панели по адресу /api/docs/ (Swagger): методы, поля, коды ответов. Там же запросы можно попробовать со своим ключом.

Своя интеграция с биллингом

Отдельного «биллингового API» нет — биллинг пользуется обычными методами с ключом admin. Услуга биллинга — это игровой сервер с полем external_id (номер услуги у вас), клиент биллинга — пользователь панели с ролью клиента (viewer).

  • Создать услугу: GET /api/users?email=…; если клиента нет — POST /api/users с role: viewer; затем POST /api/game-servers с plan_id, location_id, owner_user_id и external_id.
  • Найти сервер услуги: GET /api/game-servers?external_id=….
  • Приостановить / возобновить: POST /api/game-servers/{id}/suspend ({"reason": "…"}) / …/unsuspend.
  • Сменить тариф: PATCH /api/game-servers/{id}/plan ({"plan_id": "…"}).
  • Удалить услугу: DELETE /api/game-servers/{id}.
  • Сменить пароль клиенту: PATCH /api/users/{id} ({"password": "…"}) — его сессии завершатся.
curl -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"plan_id":"plan_…","location_id":"loc_fra","owner_user_id":"…","external_id":"billing-1042"}' \
  https://panel.example.com/api/game-servers

external_id уникален: повторный POST с тем же номером вернёт 409 и второй сервер не создаст. Поэтому запрос безопасно повторять после таймаута — найдите сервер по external_id и продолжайте. Задаётся номер только при создании.

Ресурсы сервера меняются только через тариф: для особого случая заведите отдельный тариф и переведите сервер на него.

Вебхуки

Вебхук — POST-запрос с JSON о событии на ваш URL. Интеграции → Вебхуки → Новый вебхук: URL получателя и события. Все события включает и те, что появятся в следующих версиях, поэтому получатель должен спокойно пропускать незнакомые типы.

Основные события:

  • server.created, server.started, server.stopped, server.deleted — что реально произошло на ноде (не момент запроса к API);
  • server.suspended, server.unsuspended — приостановка и возобновление;
  • server.failed — сервер не поднялся после всех попыток, в данных есть текст ошибки;
  • node.offline, node.online — нода пропала или вернулась;
  • backup.completed, backup.failed;
  • schedule.run.completed, schedule.run.failed — запуски расписаний;
  • database.created, database.deleted — базы данных серверов (без пароля).

В данных событий серверов есть external_id и connect_address — по ним удобно найти услугу и отправить клиенту адрес для подключения.

Тело запроса:

{"id": "evt_…", "type": "server.created", "created_at": "…", "data": {"id": "gs_…", "external_id": "billing-1042", "connect_address": "203.0.113.10:27015", "…": "…"}}

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

После создания вебхука панель один раз показывает секрет. Каждый запрос подписан заголовком X-Webhook-Signature: t=<unix-время>,v1=<hex>, где v1 — HMAC-SHA256 от строки <t>.<тело запроса> с этим секретом. Считайте подпись по сырому телу, до разбора JSON, и отбрасывайте запросы со старым t:

sig = dict(p.split("=", 1) for p in request.headers["X-Webhook-Signature"].split(","))
expected = hmac.new(secret.encode(), f"{sig['t']}.".encode() + request.body, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, sig["v1"]) and abs(time.time() - int(sig["t"])) < 300

Секрет потерян или утёк — Сменить секрет; старый перестаёт действовать сразу.

Повторы и гарантии

  • Доставка успешна только при ответе 2xx за 10 секунд. Редиректы считаются ошибкой.
  • После ошибки панель повторяет отправку с растущей паузой (30 секунд, минута, две… до часа), всего до 10 попыток. Потом доставка получает статус ошибки, и её можно отправить заново кнопкой Повторить в журнале доставок.
  • Одно событие может прийти дважды, а порядок не гарантирован. Отбрасывайте повторы по id события (он же в заголовке X-Webhook-Id) и не полагайтесь на очерёдность — при сомнении запросите актуальное состояние через API.
  • Проверить получателя можно кнопкой Отправить тестовое событие; код ответа и текст ошибки видны в Доставках.