무료 아이템
Tokenz Free Items API로 플레이어가 코드 없이 무료 아이템을 받게 하는 방법을 알아봅니다.
Free Items API를 사용하면 플레이어가 코드 입력이나 결제 없이 보상을 받는 이벤트를 운영할 수 있습니다. 서버에서 캠페인 상태를 확인하고 플레이어 대신 수령 요청을 보낸 후, redemption.completed 웹훅을 처리하여 보상을 지급합니다.
개요
수령 흐름은 다음과 같습니다.
- 상태 확인: 캠페인의 보상 미리 보기, 남은 수령 횟수, 다음 초기화 시각을 조회합니다.
- 수령 요청: 특정 플레이어의 수령 성공을 기록하고 사용 기록을 생성합니다.
- 웹훅을 통한 지급:
redemption.completed를 수신하고 게임 백엔드에서 보상을 지급합니다.
무료 아이템은 기프트 코드(이전 이름: Redemption Codes)와 같은 캠페인 모델 및 redemption.completed 웹훅을 사용합니다. 코드가 없다는 점이 다르며, 서버에서 기프트 코드 대신 campaignId와 playerId를 전달합니다.
두 엔드포인트의 전체 요청 및 응답 스키마는 Free Items API 레퍼런스를 참고하세요.
인증
서버에서 비밀 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 이후의 수령은 새 기간에 속합니다. 초기화는 두 수령 한도를 복원하지만 캠페인 종료 시각을 연장하지 않습니다.
수령 수명 주기
- 서버가
GET /v1/free-items/{campaignId}를 호출하여 보상과 플레이어의 남은 수령 횟수를 표시합니다. - 플레이어가 UI에서 수령을 선택합니다.
- 서버가
POST /v1/free-items/claim을 호출합니다. - Tokenz가 수령 성공 응답을 반환합니다.
- Tokenz가
redemption.completed웹훅을 전송합니다. - 백엔드가 보상을 지급합니다.
redemptionId로 중복 지급을 방지해야 합니다.
무료 아이템 상태 조회
상태 조회는 캠페인과 보상 미리 보기 및 남은 수령 횟수를 반환합니다. 캠페인이 활성이거나 예약 상태에서 시작 시각에 도달해야 하며 만료 전이어야 합니다. 한도를 모두 소모했으면 한도 오류 대신 0을 반환합니다. 상태 조회는 수령 기회를 예약하지 않으므로 실제 요청 전까지 가능 여부가 달라질 수 있습니다. playerId를 전달하면 현재 기간의 해당 플레이어 잔여 횟수도 받습니다.
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자이고 대소문자를 구분합니다. 플레이어 식별자를 참고하세요.
성공 응답 (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를 지정하지 않았거나 플레이어별 한도가 없으면 생략됩니다. 남은 횟수가 없으면 오류 대신0을 반환합니다.nextResetAt: 두 수령 한도가 다음에 초기화되는 시각입니다. 일회성 캠페인에서는 생략됩니다.
무료 아이템 수령
수령 API는 플레이어의 사용 기록을 생성합니다. 백엔드는 웹훅을 처리할 때 보상을 지급합니다. 성공하려면 수령 가능한 캠페인이어야 하고 두 한도 모두 여유가 있어야 합니다.
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자이고 대소문자를 구분합니다. 플레이어 식별자를 참고하세요.
성공 응답 (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 객체에는 code 필드가 없습니다. kind는 FREE_ITEM_CLAIM이며, 하위 호환성용 source에도 같은 값이 있지만 더 이상 권장되지 않습니다. nextPeriodStart는 redeemedAt 시점의 캠페인 반복 설정으로 계산한 다음 기간 경계입니다. 반복 무료 아이템 수령에만 포함되고 일회성 수령과 기프트 코드 사용에서는 생략됩니다. 바깥 응답의 nextResetAt은 초기화 시각을 별도로 제공합니다. 수령 웹훅을 참고하세요.
재시도와 중복 지급
수령 요청에는 멱등성 키 필드가 없습니다. 성공한 요청을 반복하면 한도가 허용하는 경우 새 사용 기록이 생성될 수 있습니다.
수령 응답을 받지 못했다면 무작정 재시도하지 마세요. 두 번째 요청이 수령 횟수를 추가 소모하고 캠페인 전체 한도도 다시 차감할 수 있습니다.
해당 캠페인과 플레이어의 redemption.completed 웹훅은 수령 성공을 나타내며, 유실된 응답에서 반환되었을 redemptionId를 포함합니다. 페이로드는 요청이 아닌 캠페인과 플레이어를 식별하므로 특정 시간 초과 요청과 항상 명확히 연결할 수 있는 것은 아닙니다. 자체 요청 로그와 대조하고, 반복 캠페인처럼 플레이어가 여러 번 수령할 수 있는 캠페인에서는 특히 주의하세요.
응답과 웹훅이 모두 없다면 현재 공개 API로 수령 내역을 다시 조회할 수 없어 판매자 측에서 결과를 확인할 수 없습니다. 재시도 대신 Tokenz 지원팀에 문의하세요.
redemptionId로 중복 지급을 방지하세요. 웹훅 전송 중복 제거만으로 두 번째 수령 요청까지 방지되지는 않습니다.
수령 웹훅
수령이 성공하면 Tokenz는 이벤트를 구독하는 엔드포인트로 redemption.completed를 전송합니다. 기프트 코드와 같은 이벤트입니다.
보상을 지급하기 전에 반드시 서명을 검증하세요. 모든 전송에는 Tokenz-Signature 헤더가 포함됩니다. 검증을 생략하면 URL을 알아내거나 추측한 사람이라면 누구나 무료 아이템을 받을 수 있습니다. 원본 요청 본문으로 검증한 후 파싱하세요. 절차는 웹훅 엔드포인트 보안 설정을 참고하세요. 아래 필드는 서명 검증에 성공한 후에만 신뢰할 수 있습니다.
{
"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: 예기치 않은 서버 오류입니다.
테스트
테스트 모드에서 무료 아이템 연동을 개발하고 테스트하세요.
- 테스트 API 키(
secret_test_접두사)로 테스트 캠페인에 접근하세요. - Tokenz 대시보드의 테스트 모드에서 테스트 캠페인을 생성합니다.
- 테스트 수령은 실제 데이터와 완전히 분리됩니다. 테스트 API 키로는 테스트 캠페인에서만, 라이브 API 키로는 라이브 캠페인에서만 수령할 수 있습니다.
- 테스트 엔티티 ID에는
_t접미사가 포함됩니다(예:redemption_1p4LPTRKB5Z_t). - 테스트 웹훅은 테스트 웹훅 엔드포인트로만 전송되며, 페이로드에
"test": true가 포함됩니다.
수령 성공과 웹훅, 플레이어 한도 소진, 전체 한도 소진, 기간 초기화, 일시 중지·만료 캠페인, 웹훅 중복 전송을 테스트하세요. redemptionId당 보상이 한 번만 지급되는지 확인하세요.