Webhook

Webhook 会在平台事件发生后立即把 GAMEMONITORING 事件发送到你的系统。它是普通的 POST 请求,body 为 JSON,方便网站、面板或游戏服务自动响应。

如果 Webhook 用于投票奖励,请在集成完成后使用此流程:投票奖励

连接

此设置会把 GAMEMONITORING 项目绑定到你的处理器,并提供用于验证传入请求的签名 token。

  1. 打开 我的项目,创建项目或选择已有项目,然后进入 Webhook 设置。
  2. 创建公开 HTTPS endpoint,接受 Content-Type: application/jsonPOST,并且不要重定向。
  3. 在 Webhook 设置中填写完整处理器 URL,例如 https://panel.example.com/gamemonitoring-webhook,并保存。
  4. 从同一区块复制签名 token,并填写到你的处理器脚本中。
  5. 在处理器中验证 signature,处理需要的 event_type,并且只在事件处理完成后返回成功的 2xx 响应。

设置完成后,从界面发送测试 Webhook 并检查投递状态。对于示例 URL,服务器必须在 /gamemonitoring-webhook 路径接受 POST

如果测试失败,请先看处理器响应:401 表示签名问题,403 或 HTML challenge 通常表示 WAF 或 bot protection,timeout 表示 URL 无法从公网访问或响应太慢。

处理器要求

  • URL 必须能从公网访问。本地地址、私有网络以及带登录名或密码的 URL 不适用。
  • 生产环境建议使用 HTTPS。HTTP 也支持,但传输中的数据保护较弱。
  • 处理器必须接受 POST 和 JSON body,且不要重定向。
  • 只有当你的系统已经处理事件后才返回 2xx。通常 204 就足够。
  • 如果事件无法安全处理,请返回失败状态。3xx4xx5xx、timeout 或连接错误都会被视为投递失败。
  • 如果使用 firewall、bot protection 或 allowlist,请把 GAMEMONITORING IP 地址加入例外。
  • 不要在响应正文中返回 token、stack trace、SQL 错误或其他内部细节。处理器响应会显示在界面中,所以错误文本必须安全且清晰。

事件数据

每个 Webhook 都会以 JSON body 投递,并包含核心字段:

  • event_type — 需要处理的事件。
  • event_id — 唯一事件 ID。请与 event_type 一起用于幂等处理和防止重复投递。
  • is_test — 标记来自界面的测试投递。
  • signature — 事件 body 的签名。
Webhook 事件示例
{
  "event_id": "9824cabb-2203-437e-9b6c-aba43dde3e4b",
  "event_type": "example.event",
  "is_test": false,
  "signature": "0ac4c97a5d934599dbd78985c4bcbb6926e77b4809d2be56333b1b25f638f064"
}

如何读取示例:event_type 表示需要处理的事件,event_id 要在改变状态前保存,is_test: true 表示只检查投递,signature 只用于请求认证。

事件逻辑取决于 event_type。对于 server.vote,请使用单独流程:投票奖励

如果 is_testtrue,验证签名并返回 2xx,但不要改变余额、发放物品或执行生产操作。

处理器响应示例

为每个进入的事件选择清晰的结果:

  • 204 No Content — 签名有效,事件已处理。测试投递和已处理过的重复事件也返回这个状态。
  • 400 Bad Request — 缺少必需字段。不要执行业务逻辑。
  • 401 Unauthorized — 签名无效。不要调用 API、不要修改数据库、不要发放奖励。
  • 500 Internal Server Error — 数据库、队列或内部服务暂时不可用。投递保持失败,修复后可以重试。

例如:处理器验证签名、保存 event_type + event_id 并处理事件后,可以立即返回 204。如果数据库不可用,返回 500,避免过早把投递标记为成功。

验证签名

签名位于 signature 字段。请在任何业务逻辑、API 请求和数据库修改之前验证它。

验证时取 body 中除 signature 外的所有字段,按 key 字母顺序排序,并用 & 拼成 key=value 字符串。Boolean 值写作 truefalse

签名字符串
event_id=9824cabb-2203-437e-9b6c-aba43dde3e4b&event_type=example.event&is_test=false

上面的示例中,签名字符串只由 event_idevent_typeis_test 组成。然后用 Webhook 设置中的 token 计算 HMAC-SHA256,并与请求中的 signature 比较。

示例中的现成签名使用演示 token paste-webhook-token-here 计算。你的处理器应使用 Webhook 设置中的 token。

在你的系统中:

  • 从排序后的 key 构建签名字符串;
  • 使用签名 token 计算 HMAC-SHA256
  • 使用固定时间比较函数把结果与 signature 对比:PHP 用 hash_equals,Node.js 用 timingSafeEqual,Python 用 compare_digest
  • 如果签名无效,返回 401

最小处理器

此示例可接收任意 Webhook:读取 JSON、验证 signature、处理测试投递、校验基础字段并返回 204。具体事件逻辑应在签名验证之后添加。

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

// 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;
}

// At this point the webhook is trusted. Add event-specific logic here.
error_log('Accepted webhook event ' . $eventType . ' #' . $eventId);

// 204 tells GAMEMONITORING that the delivery was accepted successfully.
http_response_code(204);

重复投递和去重

Webhook 使用 at-least-once 投递模型:同一个事件可能到达多次。任何会改变系统状态的动作都必须基于 event_type + event_id 做幂等处理。

event_typeevent_id 这对值保存到带唯一键的表中。如果记录已经存在,说明事件已经处理过:返回 204,不要再次修改状态。

已处理 Webhook 事件表
CREATE TABLE gamemonitoring_webhooks (
  event_type varchar(64) NOT NULL,
  event_id varchar(100) NOT NULL,
  created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (event_type, event_id)
);

当 Webhook 会修改你的数据库时,应在同一个事务中保存事件并执行动作。例如处理器发放奖励时,event_type + event_id 插入和奖励更新必须一起成功。如果插入没有创建新行,说明事件已经处理过。

不要使用昵称、用户 ID 或服务器 ID 作为去重键:同一个用户可以触发不同事件,也可能稍后重复执行允许的动作。键必须是 event_type + event_id

示例:处理器已经发放奖励,但 GAMEMONITORING 收到 204 前连接断开。重试时同一事件会再次到达。处理器必须找到已保存的 event_type + event_id,不要再次发放奖励,并返回 204

如果处理器暂时无法处理事件,请返回失败响应。修复原因后,如果该事件支持重发,可以在界面中重新发送投递。

测试和重发

测试投递(is_test: true)用于检查处理器 URL、签名和 HTTP 响应。处理器应走同一条处理流程:读取 JSON、验证 signature、识别 is_test,并返回成功的 2xx 响应。

测试事件不得修改余额、库存、角色、订阅或其他生产数据。测试只需要技术日志和 204 响应。

如果投递失败,界面中会显示状态、HTTP 状态码和处理器响应。修复原因后,如果该事件支持重发,可以重新发送失败的投递。