گیم روت
API و توسعه‌دهندگان

وبهوک‌ها

با وبهوک، گیم روت هر وقت اتفاق مهمی برای سرورتان بیفتد خودش به شما خبر می‌دهد؛ دیگر لازم نیست مدام وضعیت را بپرسید. پیام‌ها را می‌توانید مستقیم به یک کانال دیسکورد بفرستید یا به آدرس HTTP سرور خودتان.

رویدادها#

رویدادچه زمانیفیلدهای data
server.offlineسرور پس از آنلاین بودن، به بررسی‌ها جواب نمی‌دهدlastOnlineAt، failStreak
server.onlineسرور پس از آفلاین بودن دوباره جواب می‌دهدplayers، maxPlayers
server.recordرکورد جدید بازیکن همزمان ثبت شدplayers، previous
boost.createdکسی سرور شما را بوست کردboostId، userId، endsAt، boostCount، user
comment.createdنظر یا پاسخ جدیدی ثبت شدcommentId، userId، rating، status، body، isReply، isTeamReply، author

در comment.created، مقدار status برابر published یا (اگر نظرات سرور پیش از انتشار بررسی می‌شوند) pending است و body حداکثر ۵۰۰ کاراکتر اول متن است.

ساخت وبهوک#

  1. در پنل سرور به بخش API، وبهوک و ابزارک و زبانه وبهوک‌ها بروید (مجوز «اتصال شبکه‌های اجتماعی» لازم است).
  2. نوع را انتخاب کنید: کانال دیسکورد یا آدرس HTTP.
  3. آدرس و رویدادها را وارد کنید و «ساخت وبهوک» را بزنید.
  4. با «ارسال آزمایشی» یک رویداد webhook.test بفرستید و نتیجه را ببینید.

هر سرور حداکثر ۵ وبهوک دارد. برای وبهوک HTTP یک راز امضا (شروع با whsec_) ساخته می‌شود که در پنل قابل مشاهده و تعویض است.

دیسکورد#

در دیسکورد: تنظیمات کانال، Integrations، Webhooks، New Webhook و سپس Copy Webhook URL. آدرس باید به شکل https://discord.com/api/webhooks/<id>/<token> باشد. گیم روت برای هر رویداد یک پیام Embed فارسی با نام سرور، پیوند صفحه و جزئیات می‌فرستد و هیچ‌کس را منشن نمی‌کند. توکن آدرس دیسکورد پس از ذخیره در پنل پنهان نمایش داده می‌شود.

بدنه درخواست HTTP#

گیم روت برای هر رویداد یک درخواست POST با بدنه JSON می‌فرستد:

JSON
{
  "id": "dlv_4f1c2a9b7e0d3c5a8b6f1e2d",
  "event": "server.record",
  "createdAt": "2026-10-31T19:12:00.000Z",
  "server": {
    "id": "cm2x7v1q30000abcd",
    "slug": "iran-craft",
    "name": "Iran Craft",
    "game": "minecraft",
    "url": "https://gameroute.ir/servers/iran-craft"
  },
  "data": { "players": 612, "previous": 590 }
}
سرآیندمقدار
Content-Typeapplication/json
User-AgentGameroute-Webhooks/1.0
X-Gameroute-Eventنام رویداد، مثل server.record
X-Gameroute-Deliveryهمان id بدنه؛ در تلاش‌های مجدد ثابت می‌ماند
X-Gameroute-Signatureامضا به شکل t=<زمان Unix>,v1=<hex>

بررسی امضا#

امضا HMAC-SHA256 رشته <t>.<بدنه خام> با راز وبهوک است. برای اطمینان از اینکه درخواست واقعاً از گیم روت آمده:

  1. مقدار t و v1 را از سرآیند X-Gameroute-Signature جدا کنید.
  2. اگر t بیش از ۵ دقیقه با ساعت سرور شما فاصله دارد، درخواست را رد کنید (جلوگیری از ارسال دوباره درخواست‌های قدیمی).
  3. HMAC-SHA256 رشته t + . + بدنه خام را با راز وبهوک حساب کنید و با v1 به روش زمان‌ثابت مقایسه کنید.

Node.js (Express)#

JavaScript
import crypto from 'node:crypto';
import express from 'express';

export function verifyGameroute(secret, rawBody, header, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex');
  const got = String(parts.v1 ?? '');
  return got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}

const app = express();
// express.raw keeps the exact bytes for the signature check
app.post('/hooks/gameroute', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verifyGameroute(process.env.GAMEROUTE_WEBHOOK_SECRET, req.body, req.get('X-Gameroute-Signature') ?? '')) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(req.body.toString('utf8'));
  // do the slow work later; answer quickly
  queueMicrotask(() => handle(event));
  res.sendStatus(204);
});

PHP#

PHP
<?php
function gameroute_verify(string $secret, string $body, string $header, int $tolerance = 300): bool {
    $parts = [];
    foreach (explode(',', $header) as $p) {
        [$k, $v] = array_pad(explode('=', trim($p), 2), 2, '');
        $parts[$k] = $v;
    }
    $t = (int) ($parts['t'] ?? 0);
    if ($t === 0 || abs(time() - $t) > $tolerance) return false;
    $expected = hash_hmac('sha256', $t . '.' . $body, $secret);
    return hash_equals($expected, $parts['v1'] ?? '');
}

$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_GAMEROUTE_SIGNATURE'] ?? '';
if (!gameroute_verify(getenv('GAMEROUTE_WEBHOOK_SECRET'), $body, $header)) {
    http_response_code(401);
    exit;
}
$event = json_decode($body, true);
http_response_code(204);

Python (Flask)#

Python
import hashlib
import hmac
import os
import time
from flask import Flask, abort, request

def verify(secret: str, body: bytes, header: str, tolerance: int = 300) -> bool:
    parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
    try:
        t = int(parts.get("t", ""))
    except ValueError:
        return False
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

app = Flask(__name__)

@app.post("/hooks/gameroute")
def gameroute_hook():
    if not verify(os.environ["GAMEROUTE_WEBHOOK_SECRET"], request.get_data(), request.headers.get("X-Gameroute-Signature", "")):
        abort(401)
    event = request.get_json()
    return "", 204

پاسخ، تلاش مجدد و غیرفعال شدن#

  • هر پاسخ 2xx یعنی رویداد رسید. بدنه پاسخ خوانده نمی‌شود.
  • گیم روت حداکثر ۸ ثانیه منتظر پاسخ می‌ماند. کار سنگین را در صف خودتان انجام دهید و سریع جواب دهید.
  • اگر خطای شبکه، پایان مهلت، 408، 425، 429 یا 5xx بگیرد، همان رویداد را با همان id تا ۵ بار در مجموع می‌فرستد؛ با فاصله‌های تقریبی ۱، ۲، ۴ و ۸ دقیقه.
  • سایر خطاهای 4xx (مثل 401 یا 404) یعنی گیرنده عمداً رد کرده؛ دوباره فرستاده نمی‌شوند.
  • ریدایرکت‌ها (3xx) دنبال نمی‌شوند و ناموفق حساب می‌شوند. آدرس نهایی را وارد کنید.
  • بعد از ۱۵ تلاش ناموفق پشت سر هم، وبهوک خودکار غیرفعال می‌شود و مالک سرور اعلان می‌گیرد. بعد از رفع مشکل، از پنل دوباره فعالش کنید؛ شمارنده خطا صفر می‌شود.
  • ترتیب رسیدن رویدادها تضمینی نیست و یک رویداد ممکن است بیش از یک بار برسد. با X-Gameroute-Delivery تکراری‌ها را کنار بگذارید و برای ترتیب از createdAt استفاده کنید.

گزارش ارسال‌ها#

در پنل، «گزارش ارسال‌ها» ۵۰ ارسال اخیر هر وبهوک را با زمان، رویداد، کد پاسخ، پیام خطا، شماره تلاش و مدت نشان می‌دهد. گزارش‌ها ۳۰ روز (و حداکثر ۲۰۰ مورد آخر هر وبهوک) نگه داشته می‌شوند.

امنیت و محدودیت آدرس#

  • فقط آدرس‌های http و https با دامنه یا IP عمومی پذیرفته می‌شوند. آدرس‌هایی که به شبکه داخلی، localhost، IP خصوصی، link-local یا رزروشده اشاره کنند رد می‌شوند. این بررسی پس از ترجمه دامنه (DNS) و دوباره هنگام اتصال انجام می‌شود.
  • آدرس نباید نام کاربری و رمز داشته باشد. برای احراز هویت از امضا استفاده کنید.
  • HTTPS را ترجیح دهید؛ بدنه وبهوک ممکن است متن نظرات کاربران را داشته باشد.
  • اگر راز لو رفت، از گزارش ارسال‌ها «ساخت راز جدید» را بزنید. راز قبلی بلافاصله از کار می‌افتد.