Nagrody za głosy

Nagrody za głosy używają zdarzenia Webhook server.vote: handler odbiera zdarzenie, pobiera dane głosu przez API, znajduje gracza w Twoim systemie i wydaje nagrodę jeden raz.

Aby zintegrować tę metodę, najpierw skonfiguruj Webhook projektu i sprawdzaj signature. Dane głosu pobierzesz przez GET /votes/:vote_id.

Jak działa scenariusz

  1. Przyjmij zdarzenie Webhook i sprawdź signature. Jeśli is_test ma wartość true, zwróć 204 bez pobierania głosu i bez wydawania nagrody.
  2. Upewnij się, że event_type to server.vote.
  3. Odczytaj event_id: dla server.vote jest to ID głosu.
  4. Pobierz dane głosu przez GET /votes/:vote_id i znajdź gracza w swoim systemie.
  5. Zastosuj ochronę przed ponownym przetworzeniem po event_type + event_id i wydaj nagrodę w tej samej transakcji.
  6. Jeśli nagrody nie da się bezpiecznie wydać, zwróć odpowiedź z błędem, usuń przyczynę i ponów dostawę z interfejsu.

Przykład nagrody

Załóżmy, że gracz PlayerName zagłosował na serwer o ID 1, a system ma dodać mu 100 monet.

  1. GAMEMONITORING wysyła Webhook z event_type: server.vote i event_id: 9824cabb-2203-437e-9b6c-aba43dde3e4b.
  2. Handler sprawdza signature. Jeśli podpis jest błędny, zwraca 401 i kończy.
  3. Handler wywołuje GET /votes/9824cabb-2203-437e-9b6c-aba43dde3e4b i dostaje nick, serwer oraz użytkownika.
  4. W swojej bazie handler znajduje lokalne konto po nicku albo własnym powiązaniu kont.
  5. W transakcji handler stosuje ochronę przed ponownym przetworzeniem po server.vote + event_id i dodaje 100 monet.
  6. Ponowną dostawę tego samego zdarzenia obsłuż według tych samych reguł ochrony przed ponownym przetworzeniem.

Ten sam schemat sprawdza się przy przedmiotach, rolach, czasie VIP, kodach promocyjnych lub wewnętrznej kolejce.

Zdarzenie głosu

Gdy pojawi się głos na serwer, GAMEMONITORING wysyła zdarzenie server.vote. Body zawiera tylko dane dostawy: event_type, event_id, is_test i signature. Pełne dane głosu trzeba pobrać osobno.

Przykład zdarzenia
{
  "event_id": "9824cabb-2203-437e-9b6c-aba43dde3e4b",
  "event_type": "server.vote",
  "is_test": false,
  "signature": "ae83b8aba88a3a9ab3b97b1f6d65664da5628a9cb64d56d5132807bca5472e4f"
}

event_id w tym zdarzeniu jest ID głosu. Nie używaj body Webhook jako źródła nicku, serwera lub użytkownika: te dane pochodzą z API.

Pobieranie danych głosu

Użyj event_id jako vote_id i pobierz dane głosu przez GET /votes/:vote_id:

Żądanie danych głosu
curl -sS "https://api.gamemonitoring.pl/votes/9824cabb-2203-437e-9b6c-aba43dde3e4b"

Do wydania nagrody zwykle potrzebujesz response.nickname, response.server i publicznych danych response.user. Jeśli nagroda zależy od konkretnego serwera, zawsze sprawdzaj response.server.id.

Przykład mapowania: response.nickname znajduje konto gracza w bazie, response.server.id wybiera regułę nagrody dla serwera, a response.user.id można zapisać w logu wydań.

Jeśli API jest chwilowo niedostępne albo zwraca nieoczekiwaną odpowiedź, nie wydawaj nagrody w ciemno. Zwróć kod błędu, napraw przyczynę i ponów dostawę.

Pełny przykład

Przykład sprawdza podpis, pobiera dane głosu, chroni nagrodę przed ponownym wydaniem i ją nalicza. Nazwę tabeli użytkowników, pole salda i regułę wyszukiwania gracza zastąp strukturą swojego systemu.

Przed uruchomieniem przykładu skonfiguruj Webhook projektu, sprawdź GET /votes/:vote_id i zastąp SQL aktualizacji użytkownika swoim modelem kont.

php
<?php
// Set the webhook token, API URL and local database connection.
$secret = 'paste-webhook-token-here';
$apiUrl = 'https://api.gamemonitoring.pl';
$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);