投票奖励

投票奖励使用 server.vote Webhook 事件:处理器接收事件,通过 API 获取投票数据,在你的系统中找到玩家,并且只发放一次奖励。

要集成此方法,请先配置项目 Webhook,并验证 signature。投票数据通过 GET /votes/:vote_id 请求。

流程

  1. 接收 Webhook 事件并验证 signature。如果 is_testtrue,不获取投票、不发放奖励,直接返回 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.voteevent_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 事件。Body 只包含投递数据:event_typeevent_idis_testsignature。完整投票数据需要单独请求。

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

此事件中的 event_id 是投票 ID。不要将 Webhook body 作为昵称、服务器或用户数据来源:这些数据来自 API。

获取投票数据

event_id 当作 vote_id 使用,并通过 GET /votes/:vote_id 请求投票数据:

投票数据请求
curl -sS "https://api.gamemonitoring.cn/votes/9824cabb-2203-437e-9b6c-aba43dde3e4b"

发放奖励通常需要 response.nicknameresponse.server 和公开的 response.user 数据。如果奖励依赖具体服务器,请始终检查 response.server.id

映射示例:response.nickname 用来在数据库中查找玩家账号,response.server.id 选择服务器奖励规则,response.user.id 可以保存到奖励日志中。

如果 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.cn';
$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);