Oy ödülleri

Oy ödülleri server.vote ve project.vote Webhook olaylarını kullanır: handler olayı alır, API ile oy verisini çeker ve ödülü yalnızca bir kez verir.

Önce proje Webhook’unu ve signature doğrulamasını ayarlayın. Oy verisi GET /votes/:vote_id ile istenir.

Akış nasıl çalışır

  1. Webhook olayını alın ve signature doğrulayın. is_test true ise oyu çekmeden ve ödül vermeden 204 döndürün.
  2. event_type değerinin server.vote veya project.vote olduğundan emin olun.
  3. event_id değerini oy ID’si olarak kullanın.
  4. GET /votes/:vote_id ile oy verisini alın ve oyuncuyu kendi sisteminizde bulun.
  5. Tek transaction içinde event_type + event_id ile tekrar işleme korumasını uygulayın ve ödülü yalnızca yeni olay için verin.
  6. Ödül güvenli şekilde verilemiyorsa hata yanıtı döndürün. Sebep düzeltildikten sonra teslimatı arayüzden tekrar gönderin.

Ödül örneği

PlayerName oyuncusunun ID’si 1 olan server için oy verdiğini ve sisteminizin ona 100 coin eklemesi gerektiğini varsayalım.

  1. GAMEMONITORING event_type: server.vote ve event_id: 9824cabb-2203-437e-9b6c-aba43dde3e4b ile Webhook gönderir.
  2. Handler signature doğrular. İmza geçersizse 401 döndürür ve durur.
  3. Handler GET /votes/9824cabb-2203-437e-9b6c-aba43dde3e4b ister, nickname, server ve user verisini alır, ardından yerel hesabı bulur.
  4. Transaction içinde handler event_type + event_id değerini tekrar işleme koruması için kaydeder.
  5. Yeni olay için handler aynı transaction içinde 100 coin ekler.
  6. Tekrar teslimatta handler zaten kaydedilmiş olayı bulur, ödülü tekrar vermez ve 204 döndürür.

Aynı akış eşya, rol, VIP süresi, promosyon kodu veya dahili kuyruğa eklenecek işler için de uygundur.

Oy olayı

Server oyu için GAMEMONITORING server.vote, proje oyu için project.vote olayını gönderir. Olay gövdesinde yalnızca teslimat verileri vardır: event_type, event_id, is_test ve signature. Tam oy verisi ayrıca istenmelidir.

Olay örneği
{
  "event_id": "9824cabb-2203-437e-9b6c-aba43dde3e4b",
  "event_type": "server.vote",
  "is_test": false,
  "signature": "ae83b8aba88a3a9ab3b97b1f6d65664da5628a9cb64d56d5132807bca5472e4f"
}

Bu olayda event_id oy ID’sidir. Nickname, server veya user verisi için Webhook gövdesini kaynak olarak kullanmayın: bu veriler API’den gelir.

Oy verisini alma

event_id değerini vote_id olarak kullanın ve oy verisini GET /votes/:vote_id ile isteyin:

Oy verisi isteği
curl -sS "https://api.gamemonitoring.tr/votes/9824cabb-2203-437e-9b6c-aba43dde3e4b"

Ödül vermek için genellikle response.nickname, response.server ve public response.user verileri gerekir. Ödül belirli bir server’a bağlıysa her zaman response.server.id kontrol edin.

Alanları kullanma şekli: response.nickname veritabanınızdaki oyuncu hesabını bulmaya yardım eder, response.server.id server için ödül kuralını seçer, response.user.id ise oy veren GAMEMONITORING kullanıcı ID’si olarak ödül loguna yazılabilir.

API geçici olarak kullanılamıyorsa veya beklenmeyen yanıt veriyorsa doğrulama yapmadan ödül vermeyin. Hata kodu döndürün, sebebi düzeltin ve teslimatı arayüzden tekrar gönderin.

Adım 3. Oy ödülü handler’ı

Örnek temel handler’ın devamıdır: imzayı doğrular, oy verisini alır, olayı tekrar işlemeye karşı korur ve ödülü tek transaction içinde ekler. Kullanıcı tablosu adını, bakiye alanını ve oyuncu arama kuralını kendi sistem yapınızla değiştirin.

Örneği çalıştırmadan önce proje Webhook’unu ayarlayın, GET /votes/:vote_id endpoint’ini kontrol edin ve SQL user update sorgularını kendi hesap modelinizle değiştirin.

php
<?php
// Replace this token with the signing token from your GAMEMONITORING webhook settings.
$secret = 'paste-webhook-token-here';

// Add the GAMEMONITORING API URL and reward settings for vote events.
$apiUrl = 'https://api.gamemonitoring.tr';
$rewardAmount = '1.00';

// Read and decode the JSON body sent by GAMEMONITORING.
$event = 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 = ($event['is_test'] ?? false) === true;
$signingData = array_replace($event, ['is_test' => $isTest ? 'true' : 'false']);

// Build the exact signing string: all body fields except signature,
// sorted by key and joined as key=value pairs with &.
$fields = array_values(array_filter(array_keys($event), fn($field) => $field !== 'signature'));
sort($fields, SORT_STRING);

// Calculate HMAC-SHA256 with the webhook token from your settings.
$signing = implode('&', array_map(fn($field) => $field . '=' . (string) ($signingData[$field] ?? ''), $fields));
$expected = hash_hmac('sha256', $signing, $secret);
$actual = (string) ($event['signature'] ?? '');

// Reject the request before doing any work when the signature is invalid.
if (!hash_equals($expected, $actual)) {
    http_response_code(401);
    exit;
}

// Test deliveries must not change balance, inventory, roles, or production data.
if ($isTest) {
    http_response_code(204);
    exit;
}

// Real deliveries must include an event type and a stable event id.
$eventType = (string) ($event['event_type'] ?? '');
$eventId = (string) ($event['event_id'] ?? '');

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

// This reward handler processes server and project vote events.
if (!in_array($eventType, ['server.vote', 'project.vote'], true)) {
    http_response_code(204);
    exit;
}

// At this point the webhook is trusted. Load vote data before opening a database transaction.
$pdo = null;

try {
    // Load full vote data by event_id. Nickname, entity, and user data are not
    // in the webhook body. Return 500 if the API cannot confirm the vote.
    $voteUrl = $apiUrl . '/votes/' . rawurlencode($eventId);
    $voteContext = stream_context_create(['http' => ['timeout' => 5]]);
    $voteBody = @file_get_contents($voteUrl, false, $voteContext);

    if ($voteBody === false) {
        throw new RuntimeException('Vote API request failed');
    }

    $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'])) {
        throw new RuntimeException('Vote API response does not include nickname');
    }

    // Verify that the API entity matches the event before changing the account.
    $expectedEntityType = $eventType === 'project.vote' ? 'project' : 'server';
    if (($vote['entity_type'] ?? '') !== $expectedEntityType) {
        throw new RuntimeException('Vote entity type does not match event type');
    }

    // Use vote nickname to update the local account. The entity id is available in
    // vote.entity_id and in either vote.server.id or vote.project.id.
    $nickname = trim($vote['nickname']);

    if ($nickname === '') {
        throw new RuntimeException('Vote nickname is empty');
    }

    // Add your local database connection for deduplication and event-specific work.
    $pdo = new PDO('mysql:host=127.0.0.1;dbname=game;charset=utf8mb4', 'game', 'password', [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    ]);

    // Keep deduplication and the real state change in one transaction.
    // If any step fails, return 500 so the delivery can be retried.
    $pdo->beginTransaction();

    // Store the event once. This requires the table to have a unique key on
    // (event_type, event_id). Duplicate deliveries affect zero rows.
    $deduplicate = $pdo->prepare('INSERT IGNORE INTO gamemonitoring_webhooks (event_type, event_id) VALUES (?, ?)');
    $deduplicate->execute([$eventType, $eventId]);

    // The event was already processed earlier. Return success without changing
    // state again, because duplicate delivery is expected.
    if ($deduplicate->rowCount() === 0) {
        $pdo->commit();
        http_response_code(204);
        exit;
    }

    // Add event-specific database changes here. Keep them after the
    // deduplication insert and inside this same transaction.
    $balance = $pdo->prepare('UPDATE users SET balance = balance + ? WHERE nickname = ?');
    $balance->execute([$rewardAmount, $nickname]);

    // Commit only after deduplication and event-specific work both succeed.
    $pdo->commit();

    // Log only newly processed real events after the transaction succeeds.
    syslog(LOG_INFO, 'Accepted webhook event ' . $eventType . ' #' . $eventId);

    http_response_code(204);
} catch (Throwable $error) {
    // Roll back partial database work so the event can be retried safely.
    if ($pdo instanceof PDO && $pdo->inTransaction()) {
        $pdo->rollBack();
    }

    // 500 keeps the delivery failed instead of marking unfinished work as done.
    http_response_code(500);
}