玩家身份验证 Webhook
阿哈利姆通过玩家身份验证 Webhook 机制,在玩家尝试登录游戏枢纽时,向您的游戏服务器发送验证请求,您的服务器需要验证玩家身份并明确响应是允许还是拒绝其访问权限。 本文档将详细介绍玩家身份验证 Webhook 的实现方法。
该 Webhook 负责验证玩家在您游戏系统中的注册状态,不仅在初次登录时触发,还可能在玩家 session 过期或进行重要操作时被多次调用。


要求
如需正确实现阿哈利姆的玩家身份验证系统,请按照以下要求配置您的 Webhook 服务器:
- HTTPS 端点,可接收 POST Webhook 请求。
- 监听由阿哈利姆生成并 签名 的事件。
- 接收请求后,根据传入的 玩家 ID 参数查询您的游戏数据库,验证玩家身份并决定是否授予其访问 Game Hub 的权限。
- 对于验证通过的情况,返回 2xx 系列状态码和对应的 JSON 响应负载 以表示批准;对于验证未通过的情况,返回包含错误负载的 4xx 状态码 以表示拒绝;对于 服务器错误,返回 5xx。
配置步骤
以下是用于处理阿哈利姆发送的玩家验证请求的服务端函数示 例框架:
- Python
- Ruby
- Node.js
- Go
import fastapi, hashlib, hmac, json, typing
app = fastapi.FastAPI()
@app.post("/webhook")
async def webhook(request: fastapi.Request) -> dict[str, typing.Any]:
secret_key = "<YOUR_S2S_KEY>" # 请替换为您的实际 Webhook Secret Key
raw_payload = await request.body()
payload = raw_payload.decode()
timestamp = request.headers["x-aghanim-signature-timestamp"]
received_signature = request.headers["x-aghanim-signature"]
if not verify_signature(secret_key, payload, timestamp, received_signature):
raise fastapi.HTTPException(status_code=403, detail="Invalid signature")
data = json.loads(payload)
event_type = data["event_type"]
event_data = data["event_data"]
if event_type == "player.verify":
player_data = verify_player(event_data)
if not player_data:
raise fastapi.HTTPException( status_code=401, detail="Player verification failed")
return player_data
raise fastapi.HTTPException(status_code=400, detail="Unknown event type")
def verify_signature(secret_key: str, payload: str, timestamp: str, received_signature: str) -> bool:
signature_data = f"{timestamp}.{payload}"
computed_hash = hmac.new(secret_key.encode(), signature_data.encode(), hashlib.sha256)
computed_signature = computed_hash.hexdigest()
return hmac.compare_digest(computed_signature, received_signature)
def verify_player(event_data: dict[str, typing.Any]) -> dict[str, typing.Any]:
# 用于获取玩家数据的示例占位代码。
# 在实际应用中,此函数将与您的数据库或用户管理系统进行交互。
return {
"player_id": "r2d2-c3po",
"name": "Molly",
"attributes": {"level": 2},
"country": "US"
}
require 'sinatra'
require 'json'
require 'openssl'
post '/webhook' do
secret_key = "<YOUR_S2S_KEY>" # 请替换为您的实际 Webhook Secret Key
payload = request.body.read
timestamp = request.env["HTTP_X_AGHANIM_SIGNATURE_TIMESTAMP"]
received_signature = request.env["HTTP_X_AGHANIM_SIGNATURE"]
unless verify_signature(secret_key, payload, timestamp, received_signature)
halt 403, "Invalid signature"
end
data = JSON.parse(payload)
event_type = data["event_type"]
event_data = data["event_data"]
if event_type == "player.verify"
player_data = fetch_player_data(event_data)
if player_data.nil?
halt 401, "Player verification failed"
end
return player_data.to_json
end
halt 400, "Unknown event type"
end
def verify_signature(secret_key, payload, timestamp, received_signature)
signature_data = "#{timestamp}.#{payload}"
computed_signature = OpenSSL::HMAC.hexdigest('sha256', secret_key, signature_data)
OpenSSL.secure_compare(computed_signature, received_signature)
end
def fetch_player_data(event_data)
# 用于获取玩家数据的示例占位代码。
# 在实际应用中,此函数将与您的数据库或用户管理系统进行交互。
{ player_id: "r2d2-c3po", name: "Molly", attributes: { level: 2 }, country: "US" }
end
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhook', express.raw({ type: "*/*" }), async (req, res) => {
const secretKey = '<YOUR_S2S_KEY>'; // 请替换为您的实际 Webhook Secret Key
const rawPayload = req.body;
const timestamp = req.headers['x-aghanim-signature-timestamp'];
const receivedSignature = req.headers['x-aghanim-signature'];
if (!verifySignature(secretKey, rawPayload, timestamp, receivedSignature)) {
return res.status(403).send('Invalid signature');
}
const payload = JSON.parse(req.body);
const { event_type, event_data } = payload;
if (event_type === 'player.verify') {
const playerData = fetchPlayerData(event_data);
if (!playerData) {
res.status(401).json({ error: 'Player verification failed' });
return;
}
return res.json(playerData);
}
return res.status(400).send('Unknown event type');
});
function verifySignature(secretKey, payload, timestamp, receivedSignature) {
const signatureData = `${timestamp}.${payload}`;
const computedSignature = crypto
.createHmac('sha256', secretKey)
.update(signatureData)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(computedSignature), Buffer.from(receivedSignature));
}
function fetchPlayerData(event_data) {
// 用于获取玩家数据的示例占位代码。
// 在实际应用中,此函数将与您的数据库或用户管理系统进行交互。
return {
player_id: 'r2d2-c3po',
name: 'Molly',
attributes: { level: 2 },
country: 'US'
};
}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func webhookHandler(w http.ResponseWriter, r *http.Request) {
secretKey := "<YOUR_S2S_KEY>" // 请替换为您的实际 Webhook Secret Key
rawPayload, _ := ioutil.ReadAll(r.Body)
payload := string(rawPayload)
timestamp := r.Header.Get("X-Aghanim-Signature-Timestamp")
receivedSignature := r.Header.Get("X-Aghanim-Signature")
if !verifySignature(secretKey, payload, timestamp, receivedSignature) {
http.Error(w, "Invalid signature", http.StatusForbidden)
return
}
var data map[string]interface{}
if err := json.Unmarshal(rawPayload, &data); err != nil {
http.Error(w, "Invalid payload", http.StatusBadRequest)
return
}
eventType := data["event_type"].(string)
eventData := data["event_data"].(map[string]interface{})
if eventType == "player.verify" {
playerData := verifyPlayer(eventData)
if playerData == nil {
http.Error(w, "Player verification failed", http.StatusUnauthorized)
return
}
json.NewEncoder(w).Encode(playerData)
return
}
http.Error(w, "Unknown event type", http.StatusBadRequest)
}
func verifySignature(secretKey, payload, timestamp, receivedSignature string) bool {
signatureData := fmt.Sprintf("%s.%s", timestamp, payload)
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write([]byte(signatureData))
computedSignature := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(computedSignature), []byte(receivedSignature))
}
func verifyPlayer(eventData map[string]interface{}) map[string]interface{} {
// 用于获取玩家数据的示例 占位代码。
// 在实际应用中,此函数将与您的数据库或用户管理系统进行交互。
return map[string]interface{}{
"player_id": "r2d2-c3po",
"name": "Molly",
"attributes": map[string]interface{}{"level": 2},
"country": "US",
}
}
当您的函数准备就绪后:
- 部署您的端点使其可访问。
- 在Aghanim账户中注册您的端点 → Game → Webhooks → New Webhook,选择玩家验证事件类型。
或者,您也可以使用 Create Webhook API 方法在阿哈利姆中注册您的端点。
Request Schema
下面是一个 player.verify Webhook 请求示例:
- HTTP
- cURL
POST /your/webhook/uri HTTP/1.1
Content-Type: application/json
Host: your-webhook-endpoint.com
User-Agent: Aghanim/0.1.0
X-Aghanim-Signature: 2e45ed4dede5e09506717490655d2f78e96d4261040ef48cc623a780bda38812
X-Aghanim-Signature-Timestamp: 1725548450
{
"event_type": "player.verify",
"event_data": {
"player_id": "2D2R-OP3C"
},
"event_time": 1725548450,
"event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
"idempotency_key": null,
"request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
"sandbox": false,
"trigger": "hub.login",
"transaction_id": "whtx_eCacGbJVbvT",
"context": null,
"game_id": "gm_exTAyxPsVwh"
}
curl "https://your-webhook-endpoint.com/your/webhook/uri" \
-X POST \
-H "Content-Type: application/json" \
-H "User-Agent: Aghanim/0.1.0" \
-H "X-Aghanim-Signature: 2e45ed4dede5e09506717490655d2f78e96d4261040ef48cc623a780bda38812" \
-H "X-Aghanim-Signature-Timestamp: 1725548450" \
-d '{
"event_type": "player.verify",
"event_data": {
"player_id": "2D2R-OP3C"
},
"event_time": 1725548450,
"event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
"idempotency_key": null,
"request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
"sandbox": false,
"trigger": "hub.login",
"transaction_id": "whtx_eCacGbJVbvT",
"context": null,
"game_id": "gm_exTAyxPsVwh"
}'
事件 Schema
| 键名 | 类型 | 描述 |
|---|---|---|
event_id | string | 阿哈利姆生成的唯一事件标识符。 |
game_id | string | 您的游戏在阿哈利姆中的唯一标识符。 |
event_type | string | 事件的类型, player.verify 在此情境下。 |
event_time | number | 以 Unix 时间戳表示的事件发生日期。 |
event_data | EventData | 包含事件特定数据的字段,其中可能包含用于继承对象的各种键值。 |
idempotency_key | string|null | 即使出现重试情况,也能确保 Webhook 操作只执行一次。 可以是 null 具体取决于事件类型。 |
request_id | string|null | 如果事件是通过 API 请求触发的,此字段将包含对应的请求 ID。 |
sandbox | boolean | 标识事件是否来自沙盒测试环境的指示器。 |
trigger | string|null | The trigger that caused the event to be sent. |
transaction_id | string | 阿哈利姆生成的交易标识符。在同一交易过程中触发的多个事件可能共享相同的交易 ID。 |
context | object|null | 事件的相关上下文信 息。 |
EventData Schema
| 键名 | 类型 | 描述 |
|---|---|---|
player_id | string | 用于玩家身份验证的唯一 玩家 ID。 |
触发值
| 值 | 描述 |
|---|---|
hub.login | 当玩家打开游戏中心时。 |
hub.interact | 当玩家在上次访问后 6 小时返回 hub 以刷新数据时。 |
hub.purchase | 当玩家点击商店中的购买按钮以确认是否根据玩家属性该商品仍然可用时。 |
order.captured | 在购买时刻确认商品仍然可用(基于玩家的属性)。 |
hub.store.open | 当商店打开时,如果商店规则包含验证玩家操作。 |
liveops.execute_action | 当执行 LiveOps 操作时。 |
test | 在 Dashboard 中使用 "Send test event" 时。 |
Response Schema
在收到 player.verify webhook 后,您的服务器必须返回适当的 HTTP 状态码和 JSON 响应负载。