--- title: "무료 아이템" description: "Tokenz Free Items API로 플레이어가 코드 없이 무료 아이템을 받게 하는 방법을 알아봅니다." source: "https://docs.tokenz.one/ko-KR/v1/free-items" api_version: "v1" locale: "ko-KR" version_status: "legacy" docs_stage: "prod" --- # 무료 아이템 Tokenz Free Items API로 플레이어가 코드 없이 무료 아이템을 받게 하는 방법을 알아봅니다. Free Items API를 사용하면 플레이어가 코드 입력이나 결제 없이 보상을 받는 이벤트를 운영할 수 있습니다. 서버에서 캠페인 상태를 확인하고 플레이어 대신 수령 요청을 보낸 후, `redemption.completed` 웹훅을 처리하여 보상을 지급합니다. ## 개요 수령 흐름은 다음과 같습니다. - **상태 확인**: 캠페인의 보상 미리 보기, 남은 수령 횟수, 다음 초기화 시각을 조회합니다. - **수령 요청**: 특정 플레이어의 수령 성공을 기록하고 사용 기록을 생성합니다. - **웹훅을 통한 지급**: `redemption.completed`를 수신하고 게임 백엔드에서 보상을 지급합니다. 무료 아이템은 기프트 코드(이전 이름: Redemption Codes)와 같은 캠페인 모델 및 `redemption.completed` 웹훅을 사용합니다. 코드가 없다는 점이 다르며, 서버에서 기프트 코드 대신 `campaignId`와 `playerId`를 전달합니다. 두 엔드포인트의 전체 요청 및 응답 스키마는 [Free Items API 레퍼런스](https://docs.tokenz.one/ko-KR/v1/api/free-items)를 참고하세요. ## 인증 서버에서 비밀 API 키로 호출하세요. 상태 조회에는 `FreeItemStatus`, 수령에는 `FreeItemClaim` 권한 범위를 활성화하세요. 키는 백엔드에 보관하고, `playerId`는 게임에서 인증된 플레이어로부터 결정하세요. ## 플레이어 식별자 `playerId`는 판매자 측 플레이어 식별자입니다. Tokenz는 이 값을 의미를 해석하지 않는 단순 문자열로 취급하며 게임 쪽에서 조회하지 않습니다. 따라서 전송한 값만으로 플레이어를 식별합니다. Tokenz가 적용하는 규칙은 다음과 같습니다. - 앞뒤 공백을 제거합니다. - 빈 값은 허용하지 않습니다. - 최대 100자입니다. 초과하면 `request.decoding-failed`로 거부하며 임의로 잘라내지 않습니다. - 대소문자를 포함하여 정확히 일치해야 합니다. **플레이어 ID는 대소문자를 구분합니다.** `Player_123`과 `player_123`은 별개의 식별자이며 수령 한도도 따로 적용됩니다. 모든 호출 경로에서 대소문자를 포함한 동일한 고정 식별자를 사용하세요. 일반적으로 게임 내 플레이어 계정 ID가 적합합니다. Tokenz는 앞뒤 공백 제거 외에는 값을 정규화하지 않습니다. 소문자로 변환하거나 대소문자를 바꾸면 서로 다른 플레이어가 하나로 합쳐질 수 있으므로 변경하지 마세요. 같은 식별자는 어디서나 같은 플레이어를 의미합니다. 기기, 세션, 상점이 달라도 동일한 플레이어이며 하나의 수령 한도를 공유합니다. ## 캠페인 설정 및 관리 무료 아이템 캠페인은 Tokenz 대시보드에서 생성하고 관리합니다. 아직 표시되지 않는다면 Tokenz 지원팀에 계정의 사용 가능 여부를 문의하세요. 판매자는 Tokenz 대시보드에서 다음 작업을 할 수 있습니다. - 운영 기간과 보상을 설정하여 캠페인 생성 - 전체 수령 한도 및 플레이어별 수령 한도 설정 - 캠페인 전체 및 플레이어별 한도를 일정에 따라 초기화할 주기 선택 - 캠페인 일시 중지, 재개 또는 종료 ## 캠페인 상태 무료 아이템 캠페인은 기프트 코드 캠페인과 같은 상태를 사용합니다. - `draft`: 작성 중입니다. 수령 요청이 거부됩니다. 모든 필드를 수정할 수 있습니다. - `scheduled`: 게시 후 대기 중입니다. 설정한 `startAt` 시각부터 수령할 수 있습니다. - `active`: 캠페인이 진행 중이며 수령할 수 있습니다. - `paused`: 수동으로 일시 중지되었습니다. 수령 요청이 거부됩니다. `active`로 재개할 수 있습니다. - `ended`: 캠페인이 종료되었습니다. 수령 요청이 거부됩니다. 되돌릴 수 없습니다. 활성 캠페인은 수령 요청을 받습니다. 예약 캠페인도 저장된 상태가 `active`로 바뀌기 전이라도 `startAt`에 도달하면 수령할 수 있습니다. `endAt`부터는 거부됩니다. 두 시각 모두 절대 시점으로 비교하므로 UTC 자정에 끝나는 캠페인은 플레이어 위치와 관계없이 그 시점에 수령이 중단됩니다. ## 수령 한도 서로 독립적인 두 한도를 모든 수령 요청에서 확인합니다. - **전체 수령 한도**: 모든 플레이어의 최대 수령 횟수입니다. 일회성 캠페인은 전체 운영 기간, 반복 캠페인은 현재 기간에 적용됩니다. 도달하면 해당 기간의 추가 요청은 `campaign.limit-reached`로 거부됩니다. - **플레이어별 수령 한도**: 한 플레이어가 수령할 수 있는 횟수입니다. 도달하면 일회성 캠페인은 `campaign.player-limit-reached`, 반복 캠페인은 `campaign.player-period-limit-reached`로 거부됩니다. 다른 플레이어에는 영향이 없습니다. 플레이어별 한도는 `playerId` 기준입니다. 기기나 세션이 달라도 같은 플레이어로 처리됩니다. 거부된 수령 요청은 한도를 소모하지 않습니다. 플레이어 한도에 도달해 거부되어도 캠페인 전체 수령 횟수에는 영향을 주지 않습니다. ## 반복 캠페인 캠페인 전체 및 플레이어별 한도를 일정에 따라 초기화할 수 있습니다. 새 기간마다 캠페인 한도가 새로 부여되고 같은 플레이어도 다시 수령할 수 있습니다. 캠페인 생성 시 주기를 선택합니다. | 주기 | 기간 시작 | | --- | --- | | 없음(일회성) | 초기화되지 않습니다. 두 한도 모두 캠페인 전체 운영 기간에 적용됩니다. | | 매일 | 캠페인 시간대 기준 자정 | | 매주 | 캠페인 시간대 기준 월요일 자정 | | 매월 | 캠페인 시간대 기준 매월 1일 자정 | 기간은 플레이어가 아닌 캠페인의 시간대를 기준으로 계산됩니다. 주간 주기는 항상 월요일에 시작합니다. 이 시간대는 API에서 반환되지 않으므로 기간 경계를 직접 계산하지 마세요. 정확한 절대 시각인 `nextResetAt`을 사용하고, 해당 시각이 지나면 상태를 다시 조회하세요. 각 기간 경계에서 두 한도가 모두 초기화됩니다. 예를 들어 전체 한도 100회, 플레이어별 한도 1회인 일일 캠페인은 하루 100회까지, 플레이어당 1회 수령할 수 있습니다. 다음 초기화 시 다시 100회가 제공되고 각 플레이어도 다시 수령할 수 있습니다. 수령 및 상태 응답의 `nextResetAt`은 현재 기간이 끝나고 다음 기간이 시작되는 정확한 시각입니다. 카운트다운 표시에 사용하세요. 일회성 캠페인에는 없습니다. 캠페인이 계속 유효하다면 `nextResetAt` 이후의 수령은 새 기간에 속합니다. 초기화는 두 수령 한도를 복원하지만 캠페인 종료 시각을 연장하지 않습니다. ## 수령 수명 주기 1. 서버가 `GET /v1/free-items/{campaignId}`를 호출하여 보상과 플레이어의 남은 수령 횟수를 표시합니다. 2. 플레이어가 UI에서 수령을 선택합니다. 3. 서버가 `POST /v1/free-items/claim`을 호출합니다. 4. Tokenz가 수령 성공 응답을 반환합니다. 5. Tokenz가 `redemption.completed` 웹훅을 전송합니다. 6. 백엔드가 보상을 지급합니다. `redemptionId`로 중복 지급을 방지해야 합니다. ```mermaid sequenceDiagram; autonumber; participant P as 플레이어; participant M as 판매자 서버; participant T as Tokenz API; participant W as 판매자 웹훅 핸들러; M->>T: GET /v1/free-items/{campaignId}; T-->>M: 캠페인 + rewardPreview + remaining; P->>M: 무료 아이템 수령; M->>T: POST /v1/free-items/claim; T-->>M: redemption + remainingForPlayer + nextResetAt; T->>W: webhook: redemption.completed; W->>W: redemptionId로 중복 지급을 방지하며 보상 지급; ``` ## 무료 아이템 상태 조회 상태 조회는 캠페인과 보상 미리 보기 및 남은 수령 횟수를 반환합니다. 캠페인이 활성이거나 예약 상태에서 시작 시각에 도달해야 하며 만료 전이어야 합니다. 한도를 모두 소모했으면 한도 오류 대신 `0`을 반환합니다. 상태 조회는 수령 기회를 예약하지 않으므로 실제 요청 전까지 가능 여부가 달라질 수 있습니다. `playerId`를 전달하면 현재 기간의 해당 플레이어 잔여 횟수도 받습니다. ```bash 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' ``` ### 요청 필드 - `campaignId` (필수, 경로): 조회할 무료 아이템 캠페인 - `playerId` (선택, 쿼리): 판매자 측 플레이어 식별자입니다. 지정하면 해당 플레이어의 남은 수령 횟수를 반환합니다. 앞뒤 공백을 제거하며 빈 값은 허용하지 않습니다. 최대 100자이고 대소문자를 구분합니다. [플레이어 식별자](https://docs.tokenz.one/ko-KR/v1/free-items#player-identifiers)를 참고하세요. ### 성공 응답 (`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`를 지정하지 않았거나 플레이어별 한도가 없으면 생략됩니다. 남은 횟수가 없으면 오류 대신 `0`을 반환합니다. - `nextResetAt`: 두 수령 한도가 다음에 초기화되는 시각입니다. 일회성 캠페인에서는 생략됩니다. ## 무료 아이템 수령 수령 API는 플레이어의 사용 기록을 생성합니다. 백엔드는 웹훅을 처리할 때 보상을 지급합니다. 성공하려면 수령 가능한 캠페인이어야 하고 두 한도 모두 여유가 있어야 합니다. ```bash 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" }' ``` ### 요청 필드 - `campaignId` (필수): 수령할 무료 아이템 캠페인 - `playerId` (필수): 판매자 측 플레이어 식별자입니다. 앞뒤 공백을 제거하며 빈 값은 허용하지 않습니다. 최대 100자이고 대소문자를 구분합니다. [플레이어 식별자](https://docs.tokenz.one/ko-KR/v1/free-items#player-identifiers)를 참고하세요. ### 성공 응답 (`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` 객체에는 `code` 필드가 없습니다. `kind`는 `FREE_ITEM_CLAIM`이며, 하위 호환성용 `source`에도 같은 값이 있지만 더 이상 권장되지 않습니다. `nextPeriodStart`는 `redeemedAt` 시점의 캠페인 반복 설정으로 계산한 다음 기간 경계입니다. 반복 무료 아이템 수령에만 포함되고 일회성 수령과 기프트 코드 사용에서는 생략됩니다. 바깥 응답의 `nextResetAt`은 초기화 시각을 별도로 제공합니다. [수령 웹훅](https://docs.tokenz.one/ko-KR/v1/free-items#claim-webhook)을 참고하세요. ### 재시도와 중복 지급 수령 요청에는 멱등성 키 필드가 없습니다. 성공한 요청을 반복하면 한도가 허용하는 경우 새 사용 기록이 생성될 수 있습니다. 수령 응답을 받지 못했다면 **무작정 재시도하지 마세요.** 두 번째 요청이 수령 횟수를 추가 소모하고 캠페인 전체 한도도 다시 차감할 수 있습니다. 해당 캠페인과 플레이어의 `redemption.completed` 웹훅은 수령 성공을 나타내며, 유실된 응답에서 반환되었을 `redemptionId`를 포함합니다. 페이로드는 요청이 아닌 캠페인과 플레이어를 식별하므로 특정 시간 초과 요청과 항상 명확히 연결할 수 있는 것은 아닙니다. 자체 요청 로그와 대조하고, 반복 캠페인처럼 플레이어가 여러 번 수령할 수 있는 캠페인에서는 특히 주의하세요. 응답과 웹훅이 모두 없다면 현재 공개 API로 수령 내역을 다시 조회할 수 없어 판매자 측에서 결과를 확인할 수 없습니다. 재시도 대신 Tokenz 지원팀에 문의하세요. `redemptionId`로 중복 지급을 방지하세요. 웹훅 전송 중복 제거만으로 두 번째 수령 요청까지 방지되지는 않습니다. ## 수령 웹훅 수령이 성공하면 Tokenz는 이벤트를 구독하는 엔드포인트로 `redemption.completed`를 전송합니다. 기프트 코드와 같은 이벤트입니다. **보상을 지급하기 전에 반드시 서명을 검증하세요.** 모든 전송에는 `Tokenz-Signature` 헤더가 포함됩니다. 검증을 생략하면 URL을 알아내거나 추측한 사람이라면 누구나 무료 아이템을 받을 수 있습니다. 원본 요청 본문으로 검증한 후 파싱하세요. 절차는 [웹훅 엔드포인트 보안 설정](https://docs.tokenz.one/ko-KR/v1/checkout/webhooks-get-started#3.-secure-your-webhook-endpoint)을 참고하세요. 아래 필드는 서명 검증에 성공한 후에만 신뢰할 수 있습니다. ```json { "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" } } } } ``` ### 무료 아이템 수령과 기프트 코드 사용 구분 무료 아이템 수령과 기프트 코드 사용(이전 이름: Redemption Codes)은 모두 `redemption.completed`로 수신됩니다. **일반적으로 보상 지급 시 둘을 구분할 필요가 없습니다.** 두 경우 모두 `redemptionId`, `playerId`, `reward`가 있으며, 올바른 플레이어에게 한 번만 보상을 지급하는 데 필요한 정보입니다. 이 세 필드를 읽는 하나의 지급 경로를 권장합니다. 보고 등으로 구분이 필요하다면 다음을 참고하세요. - `kind`는 무료 아이템 수령이면 `FREE_ITEM_CLAIM`, 기프트 코드면 `CODE_REDEMPTION`입니다. 새 이벤트에 설정되며 이전 대기열 페이로드에는 없을 수 있습니다. - `source`는 하위 호환성을 위해 유지되지만 더 이상 권장되지 않습니다. 기존 핸들러는 읽을 수 있지만 신규 연동은 `kind`를 사용하세요. - `nextPeriodStart`는 반복 무료 아이템 수령에만 포함됩니다. 웹훅 전송 시각이 아닌 `redeemedAt` 시점의 캠페인 반복 설정으로 계산됩니다. - `code`는 기프트 코드 사용 시 사용한 코드이며, 무료 아이템 수령에는 **없습니다.** 항상 있다고 가정하지 마세요. `kind`가 없거나 알 수 없는 값이면 추측하지 마세요. 평소처럼 `redemptionId`, `playerId`, `reward`로 지급하고 대조 확인을 위해 이벤트에 표시를 남기세요. 이미 기프트 코드용 `redemption.completed`를 처리하고 있다면 첫 무료 아이템 캠페인 전에 핸들러를 검토하세요. 무조건 `code`를 읽으면 첫 수령부터 실패합니다. 최상위 이벤트 `id`는 웹훅 전송 추적에 사용하고, `redemptionId`는 백엔드의 중복 지급 방지에 사용하세요. ### 전송과 재시도 엔드포인트가 전송을 수락하지 않으면 Tokenz가 간격을 늘리고 일부 무작위성을 적용하여 자동 재시도합니다. 재시도는 첫 시도부터 최대 72시간의 기간 안에 예약됩니다. 이는 재시도를 **예약하는** 기간입니다. 그 안에 전송 성공을 보장하지 않으며 예약된 시도가 예정보다 늦게 실행될 수도 있습니다. 정확한 시도 횟수나 재시도 시각에 의존하지 마세요. 핸들러에서 유의할 두 가지 사항은 다음과 같습니다. - 수령 후 **며칠 뒤에** 전송될 수 있습니다. 지급 완료한 `redemptionId`를 짧은 만료 캐시가 아닌 영속 저장소에 보관하세요. 마지막 재시도 전에 기록이 만료되면 같은 보상이 두 번 지급될 수 있습니다. - 같은 `redemptionId`가 여러 번 도착할 수 있습니다. 이를 기준으로 지급의 멱등성을 보장하세요. ## 오류 처리 무료 아이템 엔드포인트는 구조화된 오류 객체와 함께 표준 HTTP 상태 코드를 반환합니다. ### 일반적인 오류 - `400 Bad Request` (`request.decoding-failed`): 잘못된 캠페인 ID, 비어 있거나 너무 긴 `playerId`, 또는 잘못된 요청입니다. - `401 Unauthorized`: API 키가 없거나 유효하지 않습니다. - `403 Forbidden`: API 키에 필요한 권한 범위가 없습니다. - `404 Not Found` (`entity.not-found`): 캠페인이 없거나 다른 판매자 소유이거나 API 키의 모드와 일치하지 않습니다. - `422 Unprocessable Entity` (`campaign.kind-invalid`): 무료 아이템 캠페인이 아닙니다. - `422 Unprocessable Entity` (`campaign.status-invalid`): 초안, 일시 중지, 종료 상태이거나 시작 전 예약 상태입니다. - `422 Unprocessable Entity` (`campaign.expired`): 종료 시각에 도달했습니다. 종료 상태에서는 대신 `campaign.status-invalid`가 반환될 수 있습니다. - `422 Unprocessable Entity` (`campaign.limit-reached`): 현재 기간의 한도 또는 일회성 캠페인의 전체 한도에 도달했습니다. - `422 Unprocessable Entity` (`campaign.player-limit-reached`): 플레이어별 수령 한도에 도달했습니다. - `422 Unprocessable Entity` (`campaign.player-period-limit-reached`): 플레이어가 현재 기간의 한도에 도달했습니다. `nextResetAt` 이후 재시도하세요. - `429 Too Many Requests`: 요청 빈도 제한을 초과했습니다. 두 엔드포인트 모두 API 키별로, 플레이어가 지정되면 플레이어별로도 제한됩니다. - `500 Server Error`: 예기치 않은 서버 오류입니다. ## 테스트 테스트 모드에서 무료 아이템 연동을 개발하고 테스트하세요. 1. 테스트 API 키(`secret_test_` 접두사)로 테스트 캠페인에 접근하세요. 2. Tokenz 대시보드의 테스트 모드에서 테스트 캠페인을 생성합니다. 3. 테스트 수령은 실제 데이터와 완전히 분리됩니다. 테스트 API 키로는 테스트 캠페인에서만, 라이브 API 키로는 라이브 캠페인에서만 수령할 수 있습니다. 4. 테스트 엔티티 ID에는 `_t` 접미사가 포함됩니다(예: `redemption_1p4LPTRKB5Z_t`). 5. 테스트 웹훅은 테스트 웹훅 엔드포인트로만 전송되며, 페이로드에 `"test": true`가 포함됩니다. 수령 성공과 웹훅, 플레이어 한도 소진, 전체 한도 소진, 기간 초기화, 일시 중지·만료 캠페인, 웹훅 중복 전송을 테스트하세요. `redemptionId`당 보상이 한 번만 지급되는지 확인하세요.