Webhook

Webhook доставляет событие GAMEMONITORING в вашу систему сразу после действия на платформе. Это обычный POST-запрос с JSON-телом, который позволяет сайту, панели или игровому сервису реагировать автоматически.

Для наград за голосование сначала выполните эту настройку, затем используйте отдельный сценарий: Награды за голосование.

Подключение

Эта настройка связывает проект GAMEMONITORING с вашим обработчиком и дает токен подписи для проверки входящих запросов.

  1. Откройте Мои проекты, создайте проект или выберите существующий, затем перейдите в настройки Webhook.
  2. Создайте публичный HTTPS-эндпоинт, который принимает POST с Content-Type: application/json и не перенаправляет запросы.
  3. В настройках Webhook укажите полный URL обработчика, например https://panel.example.com/gamemonitoring-webhook, и сохраните его.
  4. Скопируйте токен подписи из этого же блока и укажите его в скрипте обработчика.
  5. В обработчике проверьте signature, обработайте нужный event_type и верните успешный 2xx-ответ только после обработки события.

После настройки отправьте тестовый Webhook из интерфейса и проверьте статус доставки. Для URL из примера сервер должен принимать POST на путь /gamemonitoring-webhook.

Если тест не прошел, ориентируйтесь на ответ обработчика: 401 означает ошибку подписи, 403 или HTML challenge обычно указывает на WAF или bot protection, а тайм-аут означает, что URL недоступен из интернета или отвечает слишком долго.

Требования к обработчику

  • URL должен быть доступен из интернета. Локальные адреса, приватные сети и URL с логином или паролем не подходят.
  • HTTPS рекомендуется для продакшена. HTTP поддерживается, но слабее защищает данные при передаче.
  • Обработчик должен принимать метод POST и JSON-тело без редиректов.
  • Возвращайте 2xx только после того, как событие обработано вашей системой. Обычно достаточно 204 No Content.
  • Если событие нельзя безопасно обработать, верните код ошибки. 3xx, 4xx, 5xx, тайм-аут и ошибка подключения считаются ошибочной доставкой.
  • Если у вас firewall, bot protection или allowlist, добавьте IP-адреса GAMEMONITORING в исключения.
  • Не возвращайте в теле ответа токены, stack trace, SQL-ошибки и другие внутренние детали. Ответ обработчика отображается в интерфейсе, поэтому текст ошибки должен быть безопасным и понятным.

Данные события

Каждый Webhook приходит с JSON-телом и базовыми полями:

  • event_type — какое событие нужно обработать.
  • event_id — уникальный ID события. Используйте его вместе с event_type для идемпотентности и защиты от повторной доставки.
  • is_test — признак тестовой доставки из интерфейса.
  • signature — подпись тела события.
Пример Webhook-события
{
  "event_id": "9824cabb-2203-437e-9b6c-aba43dde3e4b",
  "event_type": "example.event",
  "is_test": false,
  "signature": "0ac4c97a5d934599dbd78985c4bcbb6926e77b4809d2be56333b1b25f638f064"
}

Пример чтения полей: event_type показывает, какое событие нужно обработать; event_id нужно сохранить перед изменением состояния; is_test: true означает, что обработчик должен только проверить прием; signature нельзя использовать в бизнес-логике, она нужна только для проверки подлинности запроса.

Логика обработки зависит от event_type. Для server.vote используйте отдельный сценарий: Награды за голосование.

Если is_test равен true, проверьте подпись и верните 2xx, но не меняйте баланс, не выдавайте предметы и не запускайте рабочие операции.

Пример ответа обработчика

Для каждого входящего события выберите один понятный результат:

  • 204 No Content — подпись корректна, событие обработано. Такой же ответ возвращайте для тестовой доставки и для уже обработанного события.
  • 400 Bad Request — в событии нет обязательных полей. Это означает ошибку в обработчике или неожиданное тело запроса, бизнес-логику запускать нельзя.
  • 401 Unauthorized — подпись неверная. Не выполняйте API-запросы, не меняйте базу и не выдавайте награды.
  • 500 Internal Server Error — ваша база, очередь или внутренняя система временно недоступна. Доставка останется ошибочной, ее можно повторить после исправления причины.

Например, если обработчик получил событие, проверил подпись, сохранил event_type + event_id и обработал событие, он может сразу вернуть 204. Если база недоступна и событие нельзя сохранить, лучше вернуть 500, чтобы доставка не была отмечена успешной раньше времени.

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

Подпись находится в поле signature. Проверяйте ее до любой бизнес-логики, запросов к API и изменений в базе.

Для проверки возьмите все поля тела события, кроме signature, отсортируйте ключи по алфавиту и соберите строку key=value через &. Булевы значения записываются как true или false.

Строка для подписи
event_id=9824cabb-2203-437e-9b6c-aba43dde3e4b&event_type=example.event&is_test=false

Для примера выше строка подписи собирается только из event_id, event_type и is_test. После этого вычислите HMAC-SHA256 с токеном подписи из настроек Webhook и сравните результат с signature из запроса.

Готовые подписи в примерах рассчитаны для демонстрационного токена paste-webhook-token-here. В своем обработчике используйте токен из настроек Webhook.

В обработчике:

  • соберите строку для подписи из отсортированных ключей;
  • вычислите HMAC-SHA256 с токеном подписи;
  • сравните результат с signature функцией с постоянным временем сравнения: hash_equals в PHP, timingSafeEqual в Node.js или compare_digest в Python;
  • верните 401, если подпись неверная.

Минимальный обработчик

Этот пример подходит для любого Webhook: он читает JSON, проверяет signature, обрабатывает тестовую доставку, проверяет базовые поля и возвращает 204. Логику конкретного события добавляйте после проверки подписи.

php
<?php
$secret = 'paste-webhook-token-here';

$event = json_decode(file_get_contents('php://input'), true) ?: [];
$isTest = ($event['is_test'] ?? false) === true;
$signingData = array_replace($event, ['is_test' => $isTest ? 'true' : 'false']);

$fields = array_values(array_filter(array_keys($event), fn($field) => $field !== 'signature'));
sort($fields, SORT_STRING);

$signing = implode('&', array_map(fn($field) => $field . '=' . (string) ($signingData[$field] ?? ''), $fields));
$expected = hash_hmac('sha256', $signing, $secret);
$actual = (string) ($event['signature'] ?? '');

if (!hash_equals($expected, $actual)) {
    http_response_code(401);
    exit;
}

if ($isTest) {
    http_response_code(204);
    exit;
}

$eventType = (string) ($event['event_type'] ?? '');
$eventId = (string) ($event['event_id'] ?? '');

if ($eventType === '' || $eventId === '') {
    http_response_code(400);
    exit;
}

error_log('Accepted webhook event ' . $eventType . ' #' . $eventId);

http_response_code(204);

Повторная доставка и защита от повторной обработки

Webhook доставляется по модели at-least-once: одно и то же событие может прийти несколько раз. Любое действие, которое меняет состояние вашей системы, должно быть идемпотентным по event_type + event_id.

Сохраняйте пару event_type и event_id в таблицу с уникальным ключом. Если запись уже существует, событие уже обработано: верните 204 и не меняйте состояние повторно.

Таблица обработанных Webhook-событий
CREATE TABLE gamemonitoring_webhooks (
  event_type varchar(64) NOT NULL,
  event_id varchar(100) NOT NULL,
  created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (event_type, event_id)
);

Когда Webhook приводит к изменению вашей базы, сохраняйте событие и выполняйте действие в одной транзакции. Если обработчик начисляет награду, вставка event_type + event_id и начисление награды должны завершиться вместе. Если вставка не создала новую строку, значит событие уже обработано.

Не используйте никнейм, ID пользователя или ID сервера как ключ дедупликации: один пользователь может инициировать разные события или повторить разрешенное действие позже. Ключом должен быть именно event_type + event_id.

Пример: обработчик начислил награду, но соединение оборвалось до того, как GAMEMONITORING получил ответ 204. Позже доставку отправляют повторно, и то же событие приходит снова. Ваш обработчик должен увидеть уже сохраненные event_type + event_id, не начислять награду повторно и вернуть 204.

Если обработчик временно не может обработать событие, верните ответ с ошибкой. После исправления причины доставку можно повторить из интерфейса, если повтор доступен для этого события.

Тесты и повторная отправка

Тестовая доставка (is_test: true) проверяет URL, подпись и HTTP-ответ обработчика. Обработчик должен пройти тот же путь обработки: прочитать JSON, проверить signature, распознать is_test и вернуть успешный 2xx-ответ.

Тестовое событие не должно менять баланс, инвентарь, роли, подписки или другие рабочие данные. Для теста достаточно технического лога и ответа 204.

Если доставка завершилась ошибкой, в интерфейсе отображаются статус, HTTP-код и ответ обработчика. После исправления причины доставку с ошибкой можно отправить повторно, если повтор доступен для этого события.