--- title: "Gift Codes" description: "Validate and redeem gift codes, grant rewards, and handle redemption webhooks." source: "https://docs.tokenz.one/en/v2/campaigns/gift-codes" api_version: "v2" locale: "en" version_status: "current" docs_stage: "prod" --- # Gift Codes Formerly Redemption Codes. API endpoints and event names are unchanged. Learn how to validate and redeem campaign codes through the Tokenz Redemption Codes API. The Redemption Codes API helps you run campaigns where players enter a code and receive a reward. Your server validates a code, confirms eligibility, redeems it, and then handles a `redemption.completed` webhook for fulfillment. ## Overview The redemption flow includes: - **Validation**: Check whether a code is redeemable and return a reward preview - **Redemption**: Consume the code for a specific player and create a redemption record - **Webhook fulfillment**: Receive `redemption.completed` and grant the reward in your game backend ## Campaign setup and management Campaigns and redemption codes are created and managed in the Tokenz Dashboard. Merchants use the Tokenz Dashboard to: - Create campaigns with active periods and reward configuration - Generate or manage redemption codes - Pause, resume, or end campaign operations ## Campaign statuses Campaigns can be in one of the following statuses: - `draft`: Work in progress. Redemption not allowed. All fields are editable. - `scheduled`: Published and queued. Automatically transitions to `active` at the configured `start_at` time. - `active`: Campaign is live and redemptions can succeed - `paused`: Manually paused. Redemptions are rejected. Can be resumed to `active` - `ended`: Campaign is finished. Redemptions are rejected. This state is irreversible `validate` and `redeem` succeed when the campaign is `active`, and also when it is `scheduled` and its `startAt` has already been reached. They are rejected from `endAt` onwards. A scheduled campaign moves to `active` on its own at `startAt`, so this only matters in the short window around the transition. ## Code statuses Redemption code statuses are derived from the campaign status and the code's own status: - `pending`: Code is valid but the campaign is not currently active (e.g., draft, scheduled, or paused). Redemption is not allowed, except for a `scheduled` campaign whose `startAt` has been reached, which does accept redemptions. - `active`: Code is ready for use. Requires the campaign to be active. This is not the only redeemable status, because a `pending` code becomes redeemable once a `scheduled` campaign reaches its `startAt`. - `deactivated`: Code is disabled. This happens when a merchant manually deactivates a code, or when the campaign has ended. The displayed code status depends on the campaign state: | Campaign status | Code status | | --- | --- | | `active` | `active` | | `draft`, `scheduled`, `paused` | `pending` | | `ended` | `deactivated` | Once a campaign has passed its `endAt`, the code cannot be redeemed even though its status may still show as `active`. The API returns a `redemptionCode.campaign-invalid` error in this case, and the same error for a campaign that has not started yet. If a merchant manually deactivates a code, it always shows as `deactivated` regardless of the campaign status. Additionally, the Tokenz Dashboard displays a code as **Redeemed** when `currentRedemptions >= maxRedemptions`, even though the API status remains `active`. This is a display-only state in the Dashboard and is not returned by the API. ## Redemption lifecycle 1. Player enters a code in your promotional campaign UI 2. Your server calls `POST /v2/redemption-codes/validate` 3. Your server shows the reward preview to the player 4. Player confirms redemption 5. Your server calls `POST /v2/redemption-codes/redeem` 6. Tokenz returns a successful redemption response 7. Tokenz sends a `redemption.completed` webhook 8. Your backend fulfills the reward and should use `redemptionId` to guard against duplicate fulfillment ```mermaid sequenceDiagram; autonumber; participant P as Player; participant M as Merchant Server; participant T as Tokenz API; participant W as Merchant Webhook Handler; P->>M: Enter redemption code; M->>T: POST /v2/redemption-codes/validate; T-->>M: Campaign + rewardPreview; P->>M: Confirm redemption; M->>T: POST /v2/redemption-codes/redeem; T-->>M: redemptionId + reward; T->>W: webhook: redemption.completed; W->>W: Fulfill reward, using redemptionId to guard against duplicate fulfillment; ``` ## Validate a code Validate checks campaign and code eligibility and returns the campaign preview plus reward preview. A successful validation requires the campaign to be active, or scheduled with its `startAt` reached, and before its `endAt`. ```bash curl --request POST \ --url https://api.tokenz.one/v2/redemption-codes/validate \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer secret_test_YOUR_KEY_HERE' \ --data '{ "redemptionCode": "SPRING2026FREE", "playerId": "player_98765" }' ``` ### Request fields - `redemptionCode` (required): Code entered by the player - `playerId` (optional): Merchant player identifier. If provided, per-player limits are also checked. If omitted, only global code and campaign limits are validated ### Success response (`200`) ```json { "campaign": { "id": "campaign_1AbCdEfGh12", "name": "Spring Campaign 2026" }, "rewardPreview": { "name": "SSR Reimu Card", "imageUrl": "https://images.example.com/rewards/reimu.png", "quantity": 1 } } ``` ## Redeem a code Redeem consumes the code for a player and creates a redemption record. A successful redemption requires the campaign to be active, or scheduled with its `startAt` reached, and before its `endAt`. ```bash curl --request POST \ --url https://api.tokenz.one/v2/redemption-codes/redeem \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer secret_test_YOUR_KEY_HERE' \ --data '{ "redemptionCode": "SPRING2026FREE", "playerId": "player_98765" }' ``` ### Request fields - `redemptionCode` (required): Code entered by the player - `playerId` (required): Merchant player identifier ### Success response (`201`) ```json { "redemptionId": "redemption_1XyZaBcDe34", "kind": "CODE_REDEMPTION", "source": "CODE_REDEMPTION", "code": "SPRING2026FREE", "playerId": "player_98765", "campaign": { "id": "campaign_1AbCdEfGh12", "name": "Spring Campaign 2026" }, "reward": { "skuRedemptionReward": { "type": "EXTERNAL_SKU", "sku": "ITEM_SSR_REIMU", "name": "SSR Reimu Card", "quantity": 1 } }, "redeemedAt": "2026-03-25T08:10:00Z" } ``` ## Redemption webhook After a successful redemption, Tokenz sends `redemption.completed` to webhook endpoints subscribed to this event. **Verify the signature before you grant anything.** Every delivery carries a `Tokenz-Signature` header, and a handler that skips verification will grant rewards to anyone who finds or guesses its URL. Verify over the raw request body, then parse. See [Secure your webhook endpoint](https://docs.tokenz.one/en/v2/checkout/webhooks-get-started#3.-secure-your-webhook-endpoint) for the steps. Treat the fields below as trustworthy only once the signature checks out. ```json { "id": "f75ff6f0-bdf9-4b25-bb96-968f8733a7e3", "object": "redemption.completed", "createdAt": "2026-03-25T08:10:00Z", "test": false, "eventData": { "type": "redemption", "version": "v2", "data": { "redemption": { "redemptionId": "redemption_1XyZaBcDe34", "campaign": { "id": "campaign_1AbCdEfGh12", "name": "Spring Campaign 2026" }, "kind": "CODE_REDEMPTION", "source": "CODE_REDEMPTION", "code": "SPRING2026FREE", "playerId": "player_98765", "reward": { "skuRedemptionReward": { "type": "EXTERNAL_SKU", "sku": "ITEM_SSR_REIMU", "name": "SSR Reimu Card", "quantity": 1 } }, "redeemedAt": "2026-03-25T08:10:00Z" } } } } ``` Use the top-level event `id` for webhook delivery tracing, and use `redemptionId` to guard against duplicate fulfillment in your backend. The same `redemption.completed` event is also sent for [Free Item claims](https://docs.tokenz.one/en/v2/free-items). Use `kind` to tell `CODE_REDEMPTION` from `FREE_ITEM_CLAIM`. The `code` field is omitted for Free Item claims. `source` is deprecated and retained for backward compatibility; new integrations should use `kind`. Older queued events may omit `kind`. ## Error handling Redemption endpoints return standard HTTP status codes with structured error objects. ### Common errors - `401 Unauthorized`: Invalid or missing API key - `403 Forbidden`: API key does not have the required scope - `404 Not Found` (`entity.not-found`): Redemption code not found - `422 Unprocessable Entity` (`campaign.limit-reached`): The campaign has reached its total redemption limit - `422 Unprocessable Entity` (`campaign.player-limit-reached`): The player has reached the per-campaign redemption limit - `422 Unprocessable Entity` (`redemptionCode.campaign-invalid`): Campaign is not active, is outside its valid time window, or code is deactivated. The error `message` field distinguishes between these reasons - `422 Unprocessable Entity` (`redemptionCode.limit-reached`): The redemption code has reached its own usage limit - `422 Unprocessable Entity` (`redemptionCode.player-limit-reached`): The player has reached the per-code redemption limit for this code - `429 Too Many Requests`: Rate limit exceeded - `500 Server Error`: Unexpected server error ## Testing Use test mode to develop and test your redemption integration: 1. Use test API keys (prefixed with `secret_test_`) to interact with test campaigns and codes 2. Create test campaigns in the Tokenz Dashboard using test mode 3. Test redemptions are fully isolated from live data — a test API key can only redeem test codes, and a live API key can only redeem live codes 4. Test entity IDs include a `_t` suffix (e.g., `redemption_1XyZaBcDe34_t`) 5. Test webhooks are delivered only to your test webhook endpoints, with `"test": true` in the payload