Награды за голосование

Награды за голосование используют Webhook-событие server.vote: обработчик получает событие, запрашивает данные голоса через API, находит игрока в вашей системе и выдает награду один раз.

Для интеграции этого метода сначала настройте Webhook проекта и проверяйте signature. Данные голоса запрашиваются через GET /votes/:vote_id.

Как работает сценарий

  1. Примите событие Webhook и проверьте signature. Если is_test равен true, верните 204 без запроса голоса и без выдачи награды.
  2. Убедитесь, что event_type равен server.vote.
  3. Возьмите event_id: для server.vote это ID голоса.
  4. Получите данные голоса через GET /votes/:vote_id и найдите игрока в своей системе.
  5. Примените защиту от повторной обработки по event_type + event_id и выдайте награду в той же транзакции.
  6. Если награду нельзя выдать безопасно, верните ответ с ошибкой, исправьте причину и повторите доставку из интерфейса.

Пример выдачи награды

Допустим, игрок PlayerName проголосовал за сервер с ID 1, а ваша система должна начислить ему 100 монет.

  1. GAMEMONITORING отправляет Webhook с event_type: server.vote и event_id: 9824cabb-2203-437e-9b6c-aba43dde3e4b.
  2. Ваш обработчик проверяет signature. Если подпись неверная, он возвращает 401 и останавливается.
  3. Обработчик запрашивает GET /votes/9824cabb-2203-437e-9b6c-aba43dde3e4b и получает никнейм, сервер и пользователя.
  4. В своей базе обработчик ищет локальный аккаунт по никнейму или по вашей привязке аккаунтов.
  5. В транзакции обработчик применяет защиту от повторной обработки по server.vote + event_id и начисляет 100 монет.
  6. Повторную доставку этого события обрабатывайте по тем же правилам защиты от повторной обработки.

Такой сценарий подходит не только для монет. Вместо баланса можно выдать предмет, роль, VIP-время, промокод или поставить задачу во внутреннюю очередь.

Событие голосования

Когда появляется голос за сервер, GAMEMONITORING отправляет событие server.vote. В теле события есть только данные доставки: event_type, event_id, is_test и signature. Полные данные голоса нужно запросить отдельно.

Пример события
{
  "event_id": "9824cabb-2203-437e-9b6c-aba43dde3e4b",
  "event_type": "server.vote",
  "is_test": false,
  "signature": "ae83b8aba88a3a9ab3b97b1f6d65664da5628a9cb64d56d5132807bca5472e4f"
}

event_id в этом событии является ID голоса. Не используйте тело Webhook как источник никнейма, сервера или пользователя: эти данные приходят из API.

Получение данных голоса

Используйте event_id как vote_id и запросите данные голоса через GET /votes/:vote_id:

Запрос данных голоса
curl -sS "https://api.gamemonitoring.ru/votes/9824cabb-2203-437e-9b6c-aba43dde3e4b"

Для выдачи награды обычно нужны response.nickname, response.server и публичные данные response.user. Если награда зависит от конкретного сервера, обязательно проверяйте response.server.id.

Пример сопоставления: response.nickname ищет аккаунт игрока в вашей базе, response.server.id выбирает правило награды для сервера, а response.user.id можно сохранить в журнале выдач как ID пользователя GAMEMONITORING, который совершил голос.

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

Полный пример

Пример проверяет подпись, получает данные голоса, защищает награду от повторной выдачи и начисляет ее игроку. Название таблицы пользователей, поле баланса и правило поиска игрока замените на структуру вашей системы.

Перед запуском примера настройте Webhook проекта, проверьте GET /votes/:vote_id и замените SQL-запросы обновления пользователя на вашу модель аккаунтов.

php
<?php
// Set the webhook token, API URL and local database connection.
$secret = 'paste-webhook-token-here';
$apiUrl = 'https://api.gamemonitoring.ru';
$rewardAmount = '1.00';
$pdo = new PDO('mysql:host=127.0.0.1;dbname=game;charset=utf8mb4', 'game', 'password', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

// Read the JSON payload sent by GAMEMONITORING.
$data = json_decode(file_get_contents('php://input'), true) ?: [];
$isTest = ($data['is_test'] ?? false) === true;
$signingData = array_replace($data, ['is_test' => $isTest ? 'true' : 'false']);

// Prepare payload keys for signature verification.
$fields = array_values(array_filter(array_keys($data), fn($field) => $field !== 'signature'));
sort($fields, SORT_STRING);

// Build the signing string and expected HMAC.
$signing = implode('&', array_map(fn($field) => $field . '=' . (string) ($signingData[$field] ?? ''), $fields));
$expected = hash_hmac('sha256', $signing, $secret);
$actual = (string) ($data['signature'] ?? '');

// Reject requests with an invalid signature.
if (!hash_equals($expected, $actual)) {
    http_response_code(401);
    exit;
}

// Acknowledge test deliveries without changing balance.
if ($isTest) {
    http_response_code(204);
    exit;
}

// Process server vote events.
if (($data['event_type'] ?? '') === 'server.vote') {
    $eventType = (string) $data['event_type'];
    $eventId = (string) $data['event_id'];

    // Load full vote data by event_id.
    $voteUrl = $apiUrl . '/votes/' . rawurlencode($eventId);
    $voteResponse = json_decode(file_get_contents($voteUrl), true) ?: [];
    $vote = $voteResponse['response'] ?? null;
    $voteServerId = (string) ($vote['server']['id'] ?? '');

    // Use vote nickname to update the local account. Use $voteServerId for per-server rules.
    $nickname = trim((string) ($vote['nickname'] ?? ''));

    if ($nickname !== '') {
        // Keep deduplication and reward update in one transaction.
        $pdo->beginTransaction();

        try {
            // Store the event once; duplicate deliveries affect zero rows.
            $reward = $pdo->prepare('INSERT IGNORE INTO gamemonitoring_webhooks (event_type, event_id) VALUES (?, ?)');
            $reward->execute([$eventType, $eventId]);

            // Reward the local user only for a newly stored event.
            if ($reward->rowCount() === 1) {
                $balance = $pdo->prepare('UPDATE users SET balance = balance + ? WHERE nickname = ?');
                $balance->execute([$rewardAmount, $nickname]);
            }

            // Commit after deduplication and balance update succeed.
            $pdo->commit();
        } catch (Throwable $error) {
            // Roll back if any database step fails.
            $pdo->rollBack();
            throw $error;
        }
    }
}

http_response_code(204);