商品移除 Webhook
阿哈利姆提供了一个集中式 Webhook,用于通知您的游戏从玩家账户中移除商品。
此 Webhook 在以下情况下触发:
- 银行或支付系统撤销玩家在游戏枢纽中的交易
- 通过阿哈利姆控制台的 Transactions 部分请求交易退款
每个事件都包含一个reason 字段,说明商品移除的原因。


要求
要使用阿哈利姆的item.remove Webhook,请按照以下要求配置您的 Webhook 服务器:
- HTTPS 端点,可接收 POST Webhook 请求。
- 监听由阿哈利姆生成并 签名 的事件。
- 处理 Webhook 负载中包含的
idempotency_key,以防止重复处理 Webhook。 - 从玩家账户中移除被撤销或退款订单中的商品,或执行其他必要操作。
- 使用 2xx 状态码表示成功处理,使用 4xx 或 5xx 状态码表示拒绝或错误。
配置步骤
以下是处理阿哈利姆 Webhooks 的端点函数模板,用于通知您的游戏移除商品:
- 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"]
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)
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"]
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
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;
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));
}
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{})
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))
}
当您的函数准备就绪后:
- 部署您的端点使其可访问。
- 在您的Aghanim账户中注册端点 → 游戏 → 网络钩子 → 新建网络钩子,选择相应的事件类型。
或者,您也可以使用 Create Webhook API 方法在阿哈利姆中注册您的端点。
Request Schema
下面是一个 item.remove 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": "item.remove",
"event_data": {
"player_id": "2D2R-OP3C",
"items": [
{
"id": "itm_exTBZQmIlDz",
"name": "水晶",
"description": "拿下这款豪华水晶宝藏,主宰游戏世界。",
"sku": "crystals",
"quantity": 480000,
"price": 9499,
"price_decimal": 94.99,
"currency": "USD",
"type": "item",
"nested_items": null
}
],
"reason": "订单退款 ord_eCacAulggpY"
},
"event_time": 1725548450,
"event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
"idempotency_key": "idmpt_aXRlb...JkX2VFS",
"request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
"sandbox": false,
"trigger": "order.refunded",
"transaction_id": "whtx_eCacGbJVbvT",
"context": {
"order": {
"id": "ord_eCacAulggpY",
"amount": 9499,
"country": "US",
"currency": "USD",
"revenue_usd": 9499,
"fees": {
"payment_system_fee_usd": 2.85,
"aghanim_fee_usd": 14.25,
"taxes_usd": 7.65
},
"receipt_number": "1234567890",
"status": "refunded",
"created_at": 1725548450,
"paid_at": 1725548460,
"creator": 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": "item.remove",
"event_data": {
"player_id": "2D2R-OP3C",
"items": [
{
"id": "itm_exTBZQmIlDz",
"name": "水晶",
"description": "拿下这款豪华水晶宝藏,主宰游戏世界。",
"sku": "crystals",
"quantity": 480000,
"price": 9499,
"price_decimal": 94.99,
"currency": "USD",
"type": "item",
"nested_items": null
}
],
"reason": "订单退款 ord_eCacAulggpY"
},
"event_time": 1725548450,
"event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
"idempotency_key": "idmpt_aXRlb...JkX2VFS",
"request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
"sandbox": false,
"trigger": "order.refunded",
"transaction_id": "whtx_eCacGbJVbvT",
"context": {
"order": {
"id": "ord_eCacAulggpY",
"amount": 9499,
"country": "US",
"currency": "USD",
"revenue_usd": 9499,
"fees": {
"payment_system_fee_usd": 2.85,
"aghanim_fee_usd": 14.25,
"taxes_usd": 7.65
},
"receipt_number": "1234567890",
"status": "refunded",
"created_at": 1725548450,
"paid_at": 1725548460,
"creator": null
}
},
"game_id": "gm_exTAyxPsVwh"
}'
事件 Schema
| 键名 | 类型 | 描述 |
|---|---|---|
event_id | string | 阿哈利姆生成的唯一事件标识符。 |
game_id | string | 您的游戏在阿哈利姆中的唯一标识符。 |
event_type | string | 事件的类型, item.remove 在此情境下。 |
event_time | number | 以 Unix 时间戳表示的事件发生日期。 |
event_data | EventData | 包含事件特定数据的字段,其中可能包含用于继承对象的各种键值。 |
idempotency_key | string | 即使出现重试情况,也能确保 Webhook 操作只执行一次。 |
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 | EventContext|null | 事件的相关上下文信息。 |
EventContext Schema
| 键名 | 类型 | 描述 |
|---|---|---|
order | OrderContext|null | 与事件相关的订单信息(如果适用)。 |
player | PlayerContext|null | (可选)玩家信息。如需启用,请在webhook设置中选择“添加玩家上下文”。 |
EventData Schema
| 键名 | 类型 | 描述 |
|---|---|---|
player_id | string | 用于玩家身份验证的唯一 玩家 ID。 |
items | Item[] | 要从玩家账户中移除的商品数组。 |
reason | string | 触发 Webhook 的可读原因。 示例:订单退款 ord_eCacAulggpY。 |
item_id | string | [已弃用] 阿哈利姆生成的商品 ID。 |
sku | string | [已弃用] 在游戏和阿哈利姆端匹配的商品 SKU。 |
Item Schema
| Key | Type | 描述 |
|---|---|---|
id | string | 阿哈利姆生成的商品唯一标识符。 |
name | string | 商品名称。 |
description | string|null | 商品描述。 |
sku | string | 商品的 SKU 标识符,必须确保在游戏系统和阿哈利姆上保持一致。 |
quantity | number | 商品数量。 |
price | number|null | 商品价格使用 最小货币单位。若商品为免费提供,则此字段值为 null. |
price_decimal | number|null | 商品价格(以十进制单位表示)。若商品为免费提供,则此字段值为 null。 |
currency | string|null | 商品价格 货币单位。若商品为免费提供,则此字段值为 null. |
type | string | 商品类型,可选值包括: item, bundle. |
nested_items | NestedItem[]|null | 礼包内包含的所有单独商品组成的数组。 |
fallback_item | Item|null | 如果无法向玩家发放主要商品,系统将提供的备选商品。 |
商品类型 bundle 可以包含多个嵌套商品。
您可以选择处理礼包的通用 SKU,也可以单独处理每个嵌套商品。
NestedItem Schema
| 键名 | 类型 | 描述 |
|---|---|---|
id | string | 阿哈利姆生成的商品唯一标识符。 |
name | string | 商品名称。 |
description | string|null | 商品描述。 |
sku | string | 商品的 SKU 标识符,必须确保在游戏系统和阿哈利姆上保持一致。 |
quantity | number | 商品数量。 |
type | string | 商品类型,可选值包括: item, bundle. |
nested_items | NestedItem[]|null | 礼包内包含的所有单独商品组成的数组。 |
OrderContext Schema
| 键名 | 类型 | 描述 |
|---|---|---|
id | string | 阿哈利姆生成的唯一订单标识符。 |
receipt_number | string | 在玩家的收据中显示的收据号码。 |
amount | number | 订单金额(以当地货币表示),在 最小货币单位. |
amount_before_discount | number | 折扣前订单金额(以当地货币表示),在 最小货币单位. |
payment_amount | number | 收取客户的最终金额(以当地货币表示,包括税费),在 最小货币单位. |
currency | string | 订单使用的三字母货币代码(遵循 ISO 4217 标准)。 |
payment_method | string | 使用的支付方式,例如 card, apple_pay, google_pay, paypal. |
country | string | 支付方式的国家的二位字母代码(遵循 ISO 3166-1 alpha-2 标准)。 |
payment_amount_usd | number | 支付金额按照估算的汇率转换为美元(例如 $112.50 显示为 112.50)。实际金额需与支付平台核对确认,一般于次月10日前完成结算。 |
revenue_usd | number | 预期净收入会按预计汇率及支付手续费折算为美元美分。实际金额需与支付平台核账确认,一般于次月 10 日前完成结算。 |
fees | Fees|null | 有关税费的信息。实际金额需与支付平台核对确认,一般于次月10日前完成结算。 |
status | string | 订单状态,可选值包括: created, captured, paid, canceled, refunded, refund_requested. |
sales_channel | string | 指示销售渠道。可以是: game_hub, checkout, android_sdk, checkout_link. |
eligible_for_reward_points | number|null | 该订单能够获得的奖励积分数量。 |
eligible_for_loyalty_points | number|null | 该订单能够获得的用户积分数量。 |
created_at | number | 以 Unix 时间戳表示的订单创建日期。 |
paid_at | number|null | 以 Unix 时间戳表示的订单支付日期。 |
creator | Creator|null | Information about creator associated with the order. |
metadata | object|null | 自定义的键值对数据对象,用于存储与订单相关的额外信息。 |
费用数据 Schema
| 键名 | 类型 | 描述 |
|---|---|---|
payment_system_fee_usd | number|null | 支付处理费用(美分)。 |
aghanim_fee_usd | number|null | 阿哈利姆服务费(美分)。 |
taxes_usd | number|null | 税费(美分)。 |
PlayerContext Schema
| 键名 | 类型 | 描述 |
|---|---|---|
player_id | string|null | 唯一的 用于玩家身 份验证的玩家标识符. |
player | object|null | 使用复合授权时的玩家ID组件。 |
attributes | Attributes | 阿哈利姆需要的基本玩家属性。 |
custom_attributes | CustomAttributes | 自定义玩家属性。 |
需要技术支持?
联系我们的集成技术团队: integration@aghanim.com