وبهوکها
با وبهوک، گیم روت هر وقت اتفاق مهمی برای سرورتان بیفتد خودش به شما خبر میدهد؛ دیگر لازم نیست مدام وضعیت را بپرسید. پیامها را میتوانید مستقیم به یک کانال دیسکورد بفرستید یا به آدرس 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 حداکثر ۵۰۰ کاراکتر اول متن است.
ساخت وبهوک#
- در پنل سرور به بخش API، وبهوک و ابزارک و زبانه وبهوکها بروید (مجوز «اتصال شبکههای اجتماعی» لازم است).
- نوع را انتخاب کنید: کانال دیسکورد یا آدرس HTTP.
- آدرس و رویدادها را وارد کنید و «ساخت وبهوک» را بزنید.
- با «ارسال آزمایشی» یک رویداد
webhook.testبفرستید و نتیجه را ببینید.
هر سرور حداکثر ۵ وبهوک دارد. برای وبهوک HTTP یک راز امضا (شروع با whsec_) ساخته میشود که در پنل قابل مشاهده و تعویض است.
دیسکورد#
در دیسکورد: تنظیمات کانال، Integrations، Webhooks، New Webhook و سپس Copy Webhook URL. آدرس باید به شکل https://discord.com/api/webhooks/<id>/<token> باشد. گیم روت برای هر رویداد یک پیام Embed فارسی با نام سرور، پیوند صفحه و جزئیات میفرستد و هیچکس را منشن نمیکند. توکن آدرس دیسکورد پس از ذخیره در پنل پنهان نمایش داده میشود.
بدنه درخواست HTTP#
گیم روت برای هر رویداد یک درخواست POST با بدنه 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-Type | application/json |
User-Agent | Gameroute-Webhooks/1.0 |
X-Gameroute-Event | نام رویداد، مثل server.record |
X-Gameroute-Delivery | همان id بدنه؛ در تلاشهای مجدد ثابت میماند |
X-Gameroute-Signature | امضا به شکل t=<زمان Unix>,v1=<hex> |
بررسی امضا#
امضا HMAC-SHA256 رشته <t>.<بدنه خام> با راز وبهوک است. برای اطمینان از اینکه درخواست واقعاً از گیم روت آمده:
- مقدار
tوv1را از سرآیندX-Gameroute-Signatureجدا کنید. - اگر
tبیش از ۵ دقیقه با ساعت سرور شما فاصله دارد، درخواست را رد کنید (جلوگیری از ارسال دوباره درخواستهای قدیمی). - HMAC-SHA256 رشته
t+.+ بدنه خام را با راز وبهوک حساب کنید و باv1به روش زمانثابت مقایسه کنید.
Node.js (Express)#
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
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)#
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 را ترجیح دهید؛ بدنه وبهوک ممکن است متن نظرات کاربران را داشته باشد.
- اگر راز لو رفت، از گزارش ارسالها «ساخت راز جدید» را بزنید. راز قبلی بلافاصله از کار میافتد.