--- title: "免费道具" description: "让玩家无需输入代码或付款即可领取活动奖励。您的服务器查询活动状态、为玩家提交领取请求,再处理 redemption.completed Webhook,在游戏中发放奖励。" source: "https://docs.tokenz.one/zh-CN/v2/free-items" api_version: "v2" locale: "zh-CN" version_status: "current" docs_stage: "prod" --- # 免费道具 让玩家无需输入代码或付款即可领取活动奖励。您的服务器查询活动状态、为玩家提交领取请求,再处理 `redemption.completed` Webhook,在游戏中发放奖励。 完整的请求与响应结构请参阅[免费道具 API 参考](https://docs.tokenz.one/zh-CN/v2/api/free-items)。 ## 身份验证与玩家标识符 从服务器使用密钥调用 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 次,前一天已领取的玩家也可再次领取。 ## 领取流程 1. 在自己的系统中保存 `campaignId`。 2. 服务器调用 `GET /v2/free-items/{campaignId}?playerId=...`,显示奖励与剩余额度。 3. 玩家点击领取后,服务器调用 `POST /v2/free-items/claim`。 4. Tokenz 创建领取记录,返回 `201`,并发送 `redemption.completed` Webhook。 5. 后端验证签名,使用 `redemptionId` 防止重复发放,再发放奖励。 ## 查询状态 `campaignId` 是必需的路径参数,`playerId` 是可选的查询参数。状态查询不会预留额度,提交领取时可用额度可能已改变。额度耗尽时,状态接口返回剩余次数 `0`,而不是额度错误。 ```bash curl --request GET \ --url 'https://api.tokenz.one/v2/free-items/campaign_1p4LPTRKB5Z_t?playerId=player_98765' \ --header 'Authorization: Bearer secret_test_YOUR_KEY_HERE' ``` ### 成功响应(200) ```json { "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 时发放。 ```bash curl --request POST \ --url https://api.tokenz.one/v2/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) ```json { "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 端点](https://docs.tokenz.one/zh-CN/v2/checkout/webhooks-get-started#3.-%E4%BF%9D%E6%8A%A4%E4%BD%A0%E7%9A%84-webhook-%E7%AB%AF%E7%82%B9)。 ```json { "id": "f75ff6f0-bdf9-4b25-bb96-968f8733a7e3", "object": "redemption.completed", "createdAt": "2026-09-10T08:10:00Z", "test": true, "eventData": { "type": "redemption", "version": "v2", "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` 只发放一次奖励。