回调事件
游戏房间的事件通过事件回调发给你的服务端:房间创建、开局、玩家拿到座位、连上和断线、离开、房间结束。大多数游戏只需要处理 game_room.closed——它带着结算结果和每个玩家的在线时长,用来发奖励、记战绩、核对计费。
在控制台「回调」页添加回调地址,在事件类型中勾选需要的 game_room.* 事件。签名校验、重试、去重和其他事件完全相同,见回调安全。

ts
import { CallbackHandler, MemoryDedupStore, MemoryNonceStore } from '@deeprespond/im-server-sdk/callback';
const handler = new CallbackHandler({
secrets: { [process.env.IM_CALLBACK_ENDPOINT_ID!]: [process.env.IM_CALLBACK_SECRET!] },
nonceStore: new MemoryNonceStore(),
dedupStore: new MemoryDedupStore(), // 多台服务器时换成共享的存储
});
handler.on('game_room.closed', async (_event, data) => {
// data.result 是你结束房间时给的结算结果;房间超时、全员离开等自动结束时为 null
await saveMatch(data.room_id, data.close_reason, data.result);
for (const m of data.members) await addPlayMinutes(m.username, m.billable_minutes);
});其他语言的写法见 Go(EventHandlers.GameRoomClosed)、Python(@handler.on_event("game_room.closed"))、Java(handler.on("game_room.closed", ...)),Node.js 的完整说明见 Node.js 回调。
事件一览
| 事件 | 什么时候 | 是否常用 |
|---|---|---|
game_room.created | 房间创建成功,已分配到游戏节点 | 按需 |
game_room.started | 开局,游戏节点开始推进帧 | 按需 |
game_room.member_joined | 玩家拿到座位(客户端调用了加入) | 按需 |
game_room.member_connected | 玩家第一次连上游戏节点 | 频繁,按需订阅 |
game_room.member_disconnected | 玩家断线,重连窗口内可以回来 | 频繁,按需订阅 |
game_room.member_left | 玩家离开:主动离开、超时、被移出、没按时连上、房间结束 | 按需 |
game_room.closed | 房间结束,带结算结果和每个玩家的在线时长 | 建议订阅 |
公共字段
每个事件的 data 都有:
| 字段 | 说明 |
|---|---|
room_id | 房间 ID |
version | 房间版本,每次状态变化加一。事件可能乱序或重复到达,用它判断新旧 |
ext | 建房时的扩展字段,原样带回 |
origin | 谁引起的:server 你的服务端、client 玩家、platform 平台、system 系统(超时、节点等) |
事件外层的 id、type、occurred_at 等见事件回调。
game_room.created
| 字段 | 说明 |
|---|---|
region | 所在大区 |
max_players、sync_mode、tick_rate | 创建时的设置 |
created_at | 创建时间 |
game_room.started
| 字段 | 说明 |
|---|---|
started_at | 开局时间 |
player_count | 开局时在座的人数 |
game_room.member_joined
| 字段 | 说明 |
|---|---|
username、seat | 玩家和座位 |
joined_at | 拿到座位的时间 |
玩家拿到座位后 60 秒内没有连上游戏节点,会以 connect_timeout 离开。
game_room.member_connected
| 字段 | 说明 |
|---|---|
username、seat | 玩家和座位 |
transport | 连接方式:udp 或 ws |
at | 连上的时间 |
只在这次加入第一次连上时发出,断线重连回来不再发出。
game_room.member_disconnected
| 字段 | 说明 |
|---|---|
username、seat | 玩家和座位 |
reason | timeout 5 秒没有收到数据;network 连接断开 |
at | 断线的时间 |
game_room.member_left
| 字段 | 说明 |
|---|---|
username、seat | 玩家和座位 |
reason | leave 主动离开;timeout 断线超过重连窗口;removed、kicked 被移出;connect_timeout 拿到座位后没按时连上;room_closed 房间结束 |
connected_ms | 这次加入累计的在线时长(毫秒) |
left_at | 离开的时间 |
game_room.closed
json
{
"room_id": "rank-8812",
"version": 6,
"origin": "server",
"ext": {"match_id": "m-8812"},
"close_reason": "closed_by_server",
"created_at": "2026-11-10T08:00:00.000Z",
"started_at": "2026-11-10T08:00:15.000Z",
"closed_at": "2026-11-10T08:31:02.000Z",
"duration_ms": 1847000,
"result": {"winner": "alice", "scores": {"alice": 12, "bob": 9}},
"members": [
{"username": "alice", "seat": 0, "connected_ms": 1845200, "billable_minutes": 31, "leave_reason": "room_closed"},
{"username": "bob", "seat": 1, "connected_ms": 1790100, "billable_minutes": 30, "leave_reason": "room_closed"}
]
}| 字段 | 说明 |
|---|---|
close_reason | 结束原因,见房间的状态 |
created_at、started_at、closed_at | 时间;没有开局时 started_at 为 null |
duration_ms | 开局到结束的时长(毫秒);没有开局为 0 |
result | 你结束房间时给的结算结果;自动结束时为 null |
members | 每次加入一项(同一座位换过人时,前后两人各一项),按座位和加入顺序 |
members[].connected_ms | 这次加入累计的在线时长(毫秒) |
members[].billable_minutes | connected_ms 按分钟向上取整,供你核对和给玩家计时。账单按天汇总后取整,与逐局相加可能略有差异,见计费 |
members[].leave_reason | 离开原因,同 game_room.member_left 的 reason |
怎么用:
- 用
room_id或ext里的对局 ID 找到你的对局,按result发奖励、记战绩。同一个事件可能重发,按事件id去重。 result为null说明房间不是由你结束的(超时、全员离开、节点故障),按你的规则判平局或作废。- 节点故障(
node_lost)时在线时长按节点最后一次上报计算,最多少算 10 秒。
