Belohnungen für Stimmen

Belohnungen für Stimmen nutzen das Webhook-Ereignis server.vote: Ihr Handler empfängt das Ereignis, ruft Stimmdaten über die API ab, findet den Spieler in Ihrem System und gibt die Belohnung genau einmal aus.

Um diese Methode zu integrieren, richten Sie zuerst den Projekt-Webhook ein und prüfen signature. Stimmdaten werden über GET /votes/:vote_id abgefragt.

Ablauf

  1. Nehmen Sie das Webhook-Ereignis an und prüfen Sie signature. Wenn is_test true ist, geben Sie 204 zurück, ohne die Stimme abzurufen und ohne eine Belohnung auszugeben.
  2. Stellen Sie sicher, dass event_type server.vote ist.
  3. Lesen Sie event_id: für server.vote ist das die ID der Stimme.
  4. Rufen Sie Stimmdaten über GET /votes/:vote_id ab und finden Sie den Spieler in Ihrem System.
  5. Wenden Sie den Schutz vor erneuter Verarbeitung über event_type + event_id an und geben Sie die Belohnung in derselben Transaktion aus.
  6. Wenn die Belohnung nicht sicher ausgegeben werden kann, geben Sie eine fehlgeschlagene Antwort zurück, beheben Sie die Ursache und wiederholen Sie die Zustellung aus der Oberfläche.

Belohnungsbeispiel

Angenommen, Spieler PlayerName hat für den Server mit ID 1 gestimmt und Ihr System soll 100 Münzen gutschreiben.

  1. GAMEMONITORING sendet einen Webhook mit event_type: server.vote und event_id: 9824cabb-2203-437e-9b6c-aba43dde3e4b.
  2. Ihr Handler prüft signature. Bei falscher Signatur gibt er 401 zurück und stoppt.
  3. Der Handler ruft GET /votes/9824cabb-2203-437e-9b6c-aba43dde3e4b auf und erhält Nickname, Server und Benutzer.
  4. In Ihrer Datenbank sucht der Handler das lokale Konto per Nickname oder eigener Kontoverknüpfung.
  5. In einer Transaktion wendet der Handler den Schutz vor erneuter Verarbeitung über server.vote + event_id an und schreibt 100 Münzen gut.
  6. Verarbeiten Sie die erneute Zustellung desselben Ereignisses nach denselben Regeln zum Schutz vor erneuter Verarbeitung.

Derselbe Ablauf eignet sich auch für Gegenstände, Rollen, VIP-Zeit, Promocodes oder interne Queues.

Stimm-Ereignis

Wenn ein Server eine Stimme erhält, sendet GAMEMONITORING das Ereignis server.vote. Der Body enthält nur Zustelldaten: event_type, event_id, is_test und signature. Vollständige Stimmdaten werden separat abgerufen.

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

event_id ist in diesem Ereignis die ID der Stimme. Verwenden Sie den Webhook-Body nicht als Quelle für Nickname, Server oder Benutzer: diese Daten kommen aus der API.

Stimmdaten abrufen

Verwenden Sie event_id als vote_id und rufen Sie die Stimmdaten über GET /votes/:vote_id ab:

Anfrage für Stimmdaten
curl -sS "https://api.gamemonitoring.de/votes/9824cabb-2203-437e-9b6c-aba43dde3e4b"

Für die Belohnung benötigen Sie normalerweise response.nickname, response.server und öffentliche Daten aus response.user. Wenn die Belohnung von einem bestimmten Server abhängt, prüfen Sie immer response.server.id.

Beispiel-Zuordnung: response.nickname findet das Spielerkonto in Ihrer Datenbank, response.server.id wählt die Belohnungsregel für den Server, und response.user.id kann im Belohnungslog gespeichert werden.

Wenn die API vorübergehend nicht verfügbar ist oder eine unerwartete Antwort liefert, geben Sie keine Belohnung ungeprüft aus. Geben Sie einen Fehlercode zurück, beheben Sie die Ursache und senden Sie die Zustellung erneut.

Vollständiges Beispiel

Das Beispiel prüft die Signatur, ruft Stimmdaten ab, verhindert doppelte Belohnungen und schreibt die Belohnung gut. Ersetzen Sie Benutzertabelle, Kontostand-Feld und Spieler-Suche durch die Struktur Ihres Systems.

Vor dem Start des Beispiels richten Sie den Projekt-Webhook ein, prüfen GET /votes/:vote_id und ersetzen die SQL-Updates durch Ihr Kontomodell.

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

// Test deliveries are signed too. Normalize the boolean value to the lowercase
// string used by GAMEMONITORING when the signature is calculated.
$isTest = ($data['is_test'] ?? false) === true;
$signingData = array_replace($data, ['is_test' => $isTest ? 'true' : 'false']);

// Prepare payload keys for signature verification: all fields except signature,
// sorted alphabetically before building the signing string.
$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 fetching vote data or 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. Nickname, server, and user data are not in the webhook body.
    $voteUrl = $apiUrl . '/votes/' . rawurlencode($eventId);
    $voteBody = @file_get_contents($voteUrl);

    // If the API is unavailable, return 500 so the delivery can be retried.
    if ($voteBody === false) {
        http_response_code(500);
        exit;
    }

    $voteResponse = json_decode($voteBody, true) ?: [];
    $vote = $voteResponse['response'] ?? null;

    // Do not issue a reward when the vote response is missing a concrete nickname.
    if (!is_array($vote) || !isset($vote['nickname']) || !is_string($vote['nickname'])) {
        http_response_code(500);
        exit;
    }

    $voteServerId = (string) ($vote['server']['id'] ?? '');

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

    if ($nickname === '') {
        http_response_code(500);
        exit;
    }

    // Keep deduplication and reward update in one transaction.
    // If the database work fails, the webhook returns 500 and can be retried.
    $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);