免费道具
让玩家无需输入兑换码或付款即可领取活动奖励。您的服务器查询活动状态、为玩家提交领取请求,再处理 redemption.completed Webhook,在游戏中发放奖励。
完整的请求与响应结构请参阅免费道具 API 参考。
身份验证与玩家标识符
从服务器使用密钥调用 API。查询状态需要 FreeItemStatus 权限,领取需要 FreeItemClaim 权限。密钥只保存在后端,playerId 应来自游戏中已登录并通过身份验证的玩家。
playerId 是您自己的玩家标识符。Tokenz 不会查询游戏账号,只根据这个字符串识别玩家。
- 会去除首尾空白,不允许空白值。
- 最多 100 个字符。超长值会返回
request.decoding-failed,不会被截断。 - 区分大小写。
Player_123和player_123是不同玩家,各有独立额度。 - 所有调用都应使用同一个稳定标识符,不要转为小写或通过其他方式合并不同标识符。
- 相同标识符在不同设备、会话或店面中共享领取额度。
活动设置与状态
在 Tokenz Dashboard 中配置活动时间、奖励、活动总额度、每位玩家的额度以及重复周期,并管理暂停、恢复和结束操作。如果看不到免费道具设置,请联系 Tokenz 支持,确认您的账号是否已开放此功能。
draft:草稿,不能领取,所有字段可编辑。scheduled:已发布,等待开始;到达startAt即可领取。active:活动进行中,可以领取。paused:已暂停,不能领取,可恢复为active。ended:已结束,不能领取,此状态不可撤销。
查询与领取要求活动类型为免费道具,状态为 active,或为 scheduled 且已到达 startAt,并且尚未到达 endAt。即使存储的状态尚未更新,已到开始时间的 scheduled 活动也可使用。时间按绝对时刻比较,玩家所在地不会改变活动结束时刻。
额度与重复周期
每次领取都会检查两个独立的额度:
- 活动总额度:所有玩家的领取次数总和。达到上限后返回
campaign.limit-reached。 - 每位玩家的额度:同一
playerId的领取次数。一次性活动达到上限返回campaign.player-limit-reached;重复活动返回campaign.player-period-limit-reached。其他玩家不受影响。
一次性活动的两个额度均适用于活动整个生命周期,不会重置。重复活动的两个额度都会在每个周期开始时重置。被拒绝的领取不会消耗任一额度。
| 频率 | 周期开始时间 |
|---|---|
| 一次性 | 不重置 |
| 每日 | 当地时间午夜 |
| 每周 | 周一当地时间午夜 |
| 每月 | 每月第一天当地时间午夜 |
使用活动的时区,而非玩家时区。API 不返回该时区,因此不要自行计算周期边界。使用 nextResetAt 显示倒计时,到达该时刻后重新查询状态。该时刻及之后的领取属于新周期。重置不会延长 endAt。
例如,每日活动总额度为 100 次、每位玩家限 1 次,则每天最多 100 位玩家各领取一次。次日重置后,总额度恢复到 100 次,前一天已领取的玩家也可再次领取。
领取流程
- 在自己的系统中保存
campaignId。 - 服务器调用
GET /v1/free-items/{campaignId}?playerId=...,显示奖励与剩余额度。 - 玩家点击领取后,服务器调用
POST /v1/free-items/claim。 - Tokenz 创建领取记录,返回
201,并发送redemption.completedWebhook。 - 后端验证签名,使用
redemptionId防止重复发放,再发放奖励。
查询状态
campaignId 是必需的路径参数,playerId 是可选的查询参数。状态查询不会预留额度,提交领取时可用额度可能已改变。额度耗尽时,状态接口返回剩余次数 0,而不是额度错误。
curl --request GET \
--url 'https://api.tokenz.one/v1/free-items/campaign_1p4LPTRKB5Z_t?playerId=player_98765' \
--header 'Authorization: Bearer secret_test_YOUR_KEY_HERE'
成功响应(200)
{
"campaign": {
"id": "campaign_1p4LPTRKB5Z_t",
"name": "Weekly Free Pack"
},
"rewardPreview": {
"name": "Marathon Energy Gift",
"imageUrl": "https://images.example.com/rewards/energy.png",
"quantity": 1
},
"campaignRemaining": 842,
"remainingForPlayer": 1,
"nextResetAt": "2026-09-14T00:00:00Z"
}
campaignRemaining:所有玩家可用的剩余次数。重复活动为当前周期,一次性活动为整个生命周期。未设置活动总额度时省略。remainingForPlayer:此玩家的剩余次数。未提供playerId或未设置玩家额度时省略。nextResetAt:两个额度下次重置的绝对时刻。一次性活动省略。
领取免费道具
请求体中的 campaignId 和 playerId 均为必填项。活动必须可领取,且两个额度都未耗尽。API 创建记录;实际奖励由您的后端处理 Webhook 时发放。
curl --request POST \
--url https://api.tokenz.one/v1/free-items/claim \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer secret_test_YOUR_KEY_HERE' \
--data '{
"campaignId": "campaign_1p4LPTRKB5Z_t",
"playerId": "player_98765"
}'
成功响应(201)
{
"redemption": {
"redemptionId": "redemption_1p4LPTRKB5Z_t",
"playerId": "player_98765",
"campaign": {
"id": "campaign_1p4LPTRKB5Z_t",
"name": "Weekly Free Pack"
},
"reward": {
"skuRedemptionReward": {
"type": "EXTERNAL_SKU",
"sku": "ITEM_ENERGY_GIFT",
"name": "Marathon Energy Gift",
"quantity": 1
}
},
"redeemedAt": "2026-09-10T08:10:00Z",
"kind": "FREE_ITEM_CLAIM",
"source": "FREE_ITEM_CLAIM",
"nextPeriodStart": "2026-09-14T00:00:00Z"
},
"remainingForPlayer": 0,
"nextResetAt": "2026-09-14T00:00:00Z"
}
remainingForPlayer 是本次领取后的剩余次数,未设置玩家额度时省略。一次性活动省略 nextResetAt。redemption 对象字段与下方 Webhook 相同。
Webhook 与奖励发放
Tokenz 向订阅此事件的端点发送 redemption.completed,与兑换码兑换使用同一事件。
发放奖励前先验证 Tokenz-Signature。 针对原始请求体验证,再解析内容。跳过验证会使伪造请求获得奖励。请参阅保护你的 webhook 端点。
{
"id": "f75ff6f0-bdf9-4b25-bb96-968f8733a7e3",
"object": "redemption.completed",
"createdAt": "2026-09-10T08:10:00Z",
"test": true,
"eventData": {
"type": "redemption",
"version": "v1",
"data": {
"redemption": {
"redemptionId": "redemption_1p4LPTRKB5Z_t",
"campaign": {
"id": "campaign_1p4LPTRKB5Z_t",
"name": "Weekly Free Pack"
},
"playerId": "player_98765",
"reward": {
"skuRedemptionReward": {
"type": "EXTERNAL_SKU",
"sku": "ITEM_ENERGY_GIFT",
"name": "Marathon Energy Gift",
"quantity": 1
}
},
"redeemedAt": "2026-09-10T08:10:00Z",
"kind": "FREE_ITEM_CLAIM",
"source": "FREE_ITEM_CLAIM",
"nextPeriodStart": "2026-09-14T00:00:00Z"
}
}
}
}
kind:免费道具为FREE_ITEM_CLAIM,兑换码为CODE_REDEMPTION。新事件都会设置此字段,但较早进入队列的事件可能省略。source:已弃用,仅为兼容现有集成保留。新集成使用kind。code:仅兑换码兑换包含此字段,免费道具领取会省略。请检查现有处理程序是否错误地要求其存在。nextPeriodStart:仅重复免费道具活动包含。它根据redeemedAt时的活动周期计算下一周期开始时刻,而非根据 Webhook 投递时间计算。一次性领取和兑换码兑换均省略。
通常只需根据 redemptionId、playerId 和 reward 共用一套发放逻辑。kind 缺失或未知时不要猜测类型,仍使用这三个字段发放,并标记事件以供核对。顶层事件 id 用于追踪投递,redemptionId 用于防止重复发放。
重试与响应丢失
领取请求没有幂等性键。额度允许时,重复请求会创建另一条领取记录。响应丢失后不要盲目重试。
Webhook 可确认领取成功,但只标识活动和玩家,未必能唯一对应某次超时请求。请结合自己的请求日志核对。如果既无响应也无 Webhook,公开 API 目前无法重新查询领取记录,请在重试前联系 Tokenz 支持。Webhook 去重不能防止重复的领取请求。
端点未接受 Webhook 时,Tokenz 会逐渐增加间隔并加入随机变化进行重试。重试安排在首次尝试后的最长 72 小时窗口内;这不是保证在窗口内送达,实际执行可能延后。不要依赖固定次数或准确时间。投递可能晚到数天,因此应将已发放的 redemptionId 持久保存,而非放入短期缓存,并保证同一 ID 的奖励只发放一次。
错误处理
400/request.decoding-failed:无效 ID、空白或超长的playerId,或格式错误的请求。401:缺少或无效的 API 密钥。403:缺少所需权限。404/entity.not-found:活动不存在、属于其他商户,或测试模式与密钥不匹配。422/campaign.kind-invalid:不是免费道具活动。422/campaign.status-invalid:草稿、暂停、结束,或尚未开始的预定活动。422/campaign.expired:已到结束时刻;结束状态也可能返回campaign.status-invalid。422/campaign.limit-reached:总额度耗尽;重复活动为当前周期,一次性活动为整个生命周期。422/campaign.player-limit-reached:玩家额度耗尽。422/campaign.player-period-limit-reached:当前周期的玩家额度耗尽,可在nextResetAt后重试。429:超过速率限制。两个接口均按 API 密钥限制;提供玩家标识符时,也按玩家限制。500:意外的服务器错误。
测试
使用以 secret_test_ 开头的密钥,在 Dashboard 测试模式下创建活动。测试与正式数据隔离,密钥只能操作模式匹配的活动。测试 ID 以 _t 结尾;测试 Webhook 仅发送到测试端点,载荷包含 "test": true。
测试成功领取与奖励发放、两个额度耗尽、周期重置、暂停与过期、重复投递。也应验证空白和超长 playerId、速率限制以及测试与正式模式隔离,并确认每个 redemptionId 只发放一次奖励。