页面获取 Webhook
page.get Webhook 用于获取 Game Hub 页面的布局:需要渲染的区块行,以及可选的用于填充这些区块的商店商品。当页面内容由游戏逻辑决定而不是在 Hub Builder 中搭建时,它非常有用。
当玩家打开已连接 page.get Webhook 的页面时,将触发该 Webhook。此类页面没有自己的区块:保存在 Aghanim 中的区块始终为空,玩家看到的是您的服务器返回的布局。
在校验购买时也会请求该 Webhook,用于确认玩家正在购买的商品仍以相同条件在售。在这类请求中,布局会被忽略,只有商品会被使用。
要求
如需接入 Aghanim 的 page.get Webhook,请按照以下要求配置您的 Webhook 服务器:
- HTTPS 端点,可接收 POST Webhook 请求。
- 监听由 Aghanim 生成并 签名 的事件。
- 使用 2xx 状态码表示成功处理,使用 4xx 或 5xx 状态码表示拒绝或错误。
- 在 10 秒内响应。该 Webhook 是在玩家等待页面加载时被请求的。
- 同步处理该请求。与
player.verify、store.get和daily_reward.get一样,page.get作为单次尝试交付,不适用重试计划。
配置步骤
- 为
page.getWebhook 处理开发一个函数。 - 部署您的端点使其可访问。
- 进入您的 Aghanim 账户后,依次导航至 游戏 → Webhooks → Add webhook,然后选择
page.get事件类型来注册您的端点。 - 在 Game Hub Builder 的区段设置中,通过 Advanced → Webhook 选择该 Webhook,页面即从中获取布局。
该设置可从区段侧边栏底部的 Add section 打开,这种方式会一次完成创建页面并选择 Webhook;已有区段则通过其所在行的 ⋯ 菜单 → Edit 打开。连接 Webhook 后,该页面上已搭建的区块会被删除且无法恢复。
或者,您也可以使用 Create Webhook API 方法在 Aghanim 中注册端点,并使用 Update Page API 方法设置 webhook_id 来连接页面。页面响应中的 layout_source 字段为只读。
响应不会被缓存:每次浏览页面都会请求该 Webhook,因此布局可以因玩家和请求而异。
触发器值
| 值 | 描述 |
|---|---|
hub.page.open | 当玩家打开使用 Webhook 布局的页面时。 |
hub.purchase | 当玩家开始购买来自该 Webhook 的商品时。 |
order.captured | 当该商品的付款被确认时。 |
test | 在 Dashboard 中使用 "Send test event" 时。 |
在 hub.purchase 和 order.captured 中,请求不携带页面信息:id 和 slug 为空,Aghanim 只会一次性读取您响应中所有区块的商品。请以与展示给玩家时相同的条件返回相同的商品——响应中缺失的商品,或条件不同的商品,都会导致购买失败。
没有页面信息时,Aghanim 无法判断由哪个 Webhook 提供了布局,因此会请求游戏中第一个已启用的 page.get Webhook。如果游戏中存在多个此类 Webhook,校验购买的可能是并未提供该页面的那一个。
有关 page.get 与其他事件类型的关系,请参阅完整的事件与触发器对照表。
Request Schema
下面是一个 page.get 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": "page.get",
"event_data": {
"player_id": "2D2R-OP3C",
"is_anonymous": false,
"locale": "en",
"id": "pg_2eAeXgQGcaie",
"slug": "daily-offers"
},
"event_time": 1725548450,
"event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
"idempotency_key": null,
"request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
"sandbox": false,
"trigger": "hub.page.open",
"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": "page.get",
"event_data": {
"player_id": "2D2R-OP3C",
"is_anonymous": false,
"locale": "en",
"id": "pg_2eAeXgQGcaie",
"slug": "daily-offers"
},
"event_time": 1725548450,
"event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
"idempotency_key": null,
"request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
"sandbox": false,
"trigger": "hub.page.open",
"transaction_id": "whtx_eCacGbJVbvT",
"context": null,
"game_id": "gm_exTAyxPsVwh"
}'
事件 Schema
| 键名 | 类型 | 描述 |
|---|---|---|
event_id | string | 阿哈利姆生成的唯一事件标识符。 |
game_id | string | 您的游戏在阿哈利姆中的唯一标识符。 |
event_type | string | 事件的类型, page.get 在此情境下。 |
event_time | number | The time the event was sent, in Unix epoch time. May differ across duplicate deliveries of the same event. |
event_data | EventData | 包含事件特定数据的字段,其中可能包含用于继承对象的各种键值。 |
idempotency_key | string|null | 即使出现重试情况,也能确保 Webhook 操作只执行一次。 可以是 null 具体取决于事件类型。 |
request_id | string|null | 如果事件是通过 API 请求触发的,此字段将包含对应的请求 ID。 |
sandbox | boolean | 标识事件是否来自沙盒测试环境的指示器。 |
trigger | string|null | 触发该事件发送的触发器。 |
transaction_id | string | 阿哈利姆生成的交易标识符。在同一交易过程中触发的多个事件可能共享相同的交易 ID。 |
context | EventContext|null | 事件的相关上下文信息。 |
EventContext Schema
| 键名 | 类型 | 描述 |
|---|---|---|
order | OrderContext|null | 与事件相关的订单信息(如果适用)。 |
player | PlayerContext|null | (可选)玩家信息。如需启用,请在webhook设置中选择“添加玩家上下文”。 |
EventData Schema
| 键名 | 类型 | 描述 |
|---|---|---|
player_id | string | 用于玩家身份验证的唯一 玩家 ID。仅在游戏未使用多重身份验证时发送。 |
player | object | 使用多重身份验证时的玩家 ID 组成字段。发送该字段时不发送 player_id。 |
is_anonymous | boolean | 指示当前用户是否未通过身份验证(匿名)。 |
locale | string | 当前玩家的区域设置代码(ISO 639-1)。在 Locales 中查找可能的区域设置完整列表。 |
id | string | 由 Aghanim 生成的唯一页面 ID。当请求与页面浏览无关时为空。 |
slug | string | 玩家请求的页面 slug。当请求与页面浏览无关时为空。 |
Response Schema
成功处理后,您的服务器应返回 2xx 范围内的状态码以及以下 JSON 数据:
| 键名 | 类型 | 描述 | 是否必需 |
|---|---|---|---|
blocks | [[Block]] | 自上而下渲染的区块行。最多 50 行,每行最多 10 个区块。 | 是 |
页面的组成完全由您的服务器控制:blocks 的每个元素为一行,行的顺序以及行内区块的顺序就是玩家看到的顺序。例如,[[ImageBannerBlock], [StoreBlock]] 会在商店上方渲染横幅,而 [[StoreBlock], [ImageBannerBlock]] 会先渲染商店。
支持三种区块类型,通过 block 字段区分:StoreBlock、ImageBannerBlock 和 DailyRewardsBlock。包含其他区块类型的响应将被拒绝,页面会在没有区块的情况下渲染。
StoreBlock Schema
| 键名 | 类型 | 描述 | 是否必需 |
|---|---|---|---|
block | string | 区块类型标识符。必须为 StoreBlock。 | 是 |
id | string | 您自己为该区块设置的标识符。 | 否 |
store_key | string | 该区块所渲染商店的键。当区块携带 items 时必填。 | 否 |
items | [Item|BundleItem] | 该区块的商品,格式与 store.get 响应相同。每个区块最多 300 个商品。 | 否 |
若没有 store_key,该区块将渲染您在 Aghanim 中配置的游戏商店设置。
ImageBannerBlock Schema
| 键名 | 类型 | 描述 | 是否必需 |
|---|---|---|---|
block | string | 区块类型标识符。必须为 ImageBannerBlock。 | 是 |
image_url | string | 横幅图片。 | 是 |
title | string | 显示在横幅上的标题。 | 否 |
text | string | 显示在横幅上的文本。 | 否 |
button_text | string | 横幅按钮的文本。 | 否 |
link | string | 横幅跳转的路径,相对于 Game Hub 根目录(例如,/store)。 | 否 |
id | string | 您自己为该区块设置的标识符。 | 否 |
请求中包含玩家的 locale,因此图片和文本应直接返回已本地化的内容——玩家看到的就是您响应中的值。
DailyRewardsBlock Schema
| 键名 | 类型 | 描述 | 是否必填 |
|---|---|---|---|
block | string | 区块类型判别字段,必须为 DailyRewardsBlock。 | 是 |
variant | string | 区块的渲染布局:expanded、compact 或 compact_mobile,默认为 expanded。 | 否 |
non_compliance | boolean | compact 布局是否使用 non-compliance 按钮,其他布局会忽略该字段。 | 否 |
id | string | 您自己为该区块设置的标识符。 | 否 |
该区块渲染您在 Aghanim 中配置的游戏每日奖励方案。page.get 响应只负责把它放到页面上并决定其外观,奖励内容来自方案本身;若方案由 Webhook 驱动,则来自 daily_reward.get Webhook。
商店商品
在 StoreBlock 中传入 items,即可在一次响应中同时提供布局和商店内容。这些商品会经过与 store.get 商品相同的价格、赠品和购买限制处理,并且 Aghanim 不会再为它们所填充的商店请求 store.get。
每个带 items 的区块都通过 store_key 指定其商店——即 Aghanim Dashboard → Stores → 对应商店 → Settings 中商店的 Key。
商店类型必须通过 Key 旁边的 Type 字段设为 Webhook。类型在创建商店时选定,之后无法更改。创建这类商店时还需要指定一个 store.get Webhook,但当商品随 page.get 一起送达时它不会被调用。
无论区块是否携带商品,store_key 在一次响应的所有商店区块中都必须唯一,因此每个携带商品的区块都需要独立的商店。键重复属于结构错误:整个响应会被拒绝,页面渲染为空。
每个页面最多可有 5 个区块携带商品,"items": [] 同样算作携带商品。
items 的三种状态各不相同:
- 没有
items字段——该区块渲染它所指定的商店;未指定时,则渲染游戏中配置的商店内容。Webhook 类型的商店由其自身的store.get调用填充。 "items": []——商店渲染为空。- 非空列表——商店只渲染这些商品。
当 store_key 未匹配到游戏中的任何商店、匹配到已归档或已删除的商店,或匹配到的商店类型不是 Webhook 时,这些商品会被丢弃。区块本身仍会被渲染,并由游戏中配置的商店内容填充。
同一 SKU 若发送到一个页面的两个区块,会在每个区块中各渲染一次,共两次。商品不会跨区块去重。
成功响应示例
{
"blocks": [
[
{
"block": "ImageBannerBlock",
"id": "summer-banner",
"image_url": "https://example.com/summer-sale.png",
"title": "Summer sale",
"link": "/store"
}
],
[
{
"block": "StoreBlock",
"id": "daily-store",
"store_key": "daily-deals",
"items": [
{
"sku": "crystals"
},
{
"sku": "shield",
"start_at": 1630000000,
"end_at": 1635000000
},
{
"sku": "fly_bundle",
"price": 2000,
"name": "Fly Bundle",
"description": "Fly Bundle Description",
"image_url": "https://example.com/fly-bundle.png",
"nested_items": [
{
"sku": "nested_item_sku_1"
},
{
"sku": "nested_item_sku_2",
"quantity": 200
}
]
}
]
}
],
[
{
"block": "StoreBlock",
"id": "regular-store"
}
]
]
}
错误处理
页面接口不会因为该 Webhook 而失败。如果您的服务器超时、返回非 2xx 状态,或返回不符合响应结构的数据,玩家仍会收到页面——但布局为空,本次页面浏览的区块会丢失。投递失败不会重试:布局只在玩家等待页面时才有意义。您可以在 Aghanim Dashboard → Game → Webhooks 的 Webhook 日志中查看请求以及被拒绝的响应。
需要技术支持?
联系我们的集成技术团队: [email protected]