Verified Parent Linking (VPL)
Verified parent linking (VPL)은 플레이어가 자신의 부모를 제품으로 초대할 수 있게 해 줍니다. 플레이어가 초대를 보내고 부모가 신원을 확인하면, 그때부터 부모는 해당 플레이어의 컨트롤을 확인하고 설정할 수 있습니다. 초대가 진행 중인 동안에도 제품에서 차단되는 것은 없으며, 부모가 끝내 수락하지 않아도 아무것도 망가지지 않습니다.
이 가이드는 세션 생성, 초대 전송, 상태 추적, 초대 취소, 그리고 어느 쪽에서든 발생하는 연결 해제까지 전체 흐름을 다룹니다.
Verified parent linking (VPL)에서 VPL이 왜 게이트가 아니라 링크인지, 그리고 왜 VPC와 상호 배타적인지 설명합니다. 거기에 정리된 적격성 규칙이 이 가이드의 호출이 성공할지를 결정합니다.
사전 준비
시작하기 전에 다음이 필요합니다.
- k-ID 제품: Compliance Studio에서 제품을 만들고 구성합니다.
- API 키: 제품의 Developer Settings 페이지에서 발급합니다. 이 가이드의 모든 호출은 서버 간 통신입니다. 인증을 참고하세요.
- 웹훅 엔드포인트:
Challenge.StateChange와Session.Unlink를 받을 HTTPS 엔드포인트입니다. 웹훅을 참고하세요. - 처리할 이벤트 구독: 엔드포인트는 구독한 이벤트 유형만 받으며, 나머지는 k-ID가 전달을 시도하지도 오류를 반환하지도 않고 버립니다. Compliance Studio에서 해당 제품의 Developer Settings 페이지를 열고 그 엔드포인트에 대해
Challenge.StateChange와Session.Unlink를 선택하세요. - 조직에 VPL이 활성화되어 있을 것: k-ID에 연락해 이 기능을 조직에서 활성화해 달라고 요청하세요.
모든 예시는 라이브 기본 URL https://game-api.k-id.com/api/v1, Content-Type: application/json, Authorization: Bearer <api-key> 헤더를 사용합니다. 테스트 모드에서는 https://game-api.test.k-id.com/api/v1을 사용하세요.
1단계: 플레이어의 세션 만들기
POST /age-gate/check를 호출해 플레이어의 세션을 만듭니다.
POST /age-gate/check
{
"jurisdiction": "US-CA",
"age": 17
}
age, dateOfBirth, kuid(캐시해 둔 kuid가 있는 재방문 플레이어의 경우) 중 최대 하나만 전달하세요. 둘 이상 전달하면 400이 반환됩니다. 이 엔드포인트가 받아들이는 다른 연령 신호는 /age-gate/check를 참고하세요.
성공 응답에는 세션이 담겨 있습니다.
{
"status": "PASS",
"session": {
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"status": "ACTIVE",
"managedBy": "PLAYER",
"hasApproverEmail": false
}
}
sessionId를 자체 사용자 레코드에 저장하세요. 이 가이드의 모든 호출이 이 값을 키로 삼으며, 연결과 연결 해제, 재연결을 거쳐도 그대로 유지됩니다.
상태가 PASS가 아니라 CHALLENGE라면 플레이어가 해당 관할권의 액세스 연령 미만이므로 부모 동의가 필요합니다. VPC로 안내하고 초대 엔드포인트는 호출하지 마세요. 부모를 초대할 세션이 존재하지 않기 때문입니다.
PASS로 돌아온 세션이라도 GUARDIAN 관리 권한이 있으면 VPL 대상이 아닙니다. 초대 옵션을 노출하기 전에 세션의 permissions[]를 확인하거나, 3단계의 FEATURE_DISABLED 오류를 처리하세요.
2단계: 초대 옵션 노출하기
설정, 플레이어 프로필, 온보딩 등 제품에 어울리는 위치에 배치하세요. 플레이어가 누르면 부모의 이메일 주소를 수집합니다.
VPL은 선택 사항이며 아무것도 차단하지 않으므로, 이는 중간에 끼어드는 화면이 아니라 일반적인 제품 요소로 다루면 됩니다. 플레이어는 계속 무시해도 괜찮습니다.
3단계: 초대 보내기
POST /challenge/invite-parent를 호출합니다.
POST /challenge/invite-parent
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"parentEmail": "parent@example.com",
"playerName": "Alex",
"locale": "en"
}
| 필드 | 필수 | 설명 |
|---|---|---|
sessionId | 예 | 1단계의 세션 |
parentEmail | 예 | 초대를 보낼 주소 |
playerName | 아니요 | 플레이어의 표시 이름으로 1자에서 63자까지. 초대 이메일에서 부모에게 표시됩니다. 생략하면 부모에게 일반 자리 표시자가 보입니다 |
locale | 아니요 | 초대 이메일의 IETF BCP 47 태그. 생략하면 k-ID가 해당 부모를 이미 알고 있는 경우 그 부모의 저장된 언어를 사용하고, 그다음 제품의 기본 언어, 마지막으로 영어로 대체됩니다 |
{
"status": "CHALLENGE",
"challenge": {
"challengeId": "ae6d4729-af32-42ea-8ef2-ff46c7664802",
"type": "CHALLENGE_PARENT_INVITE"
}
}
한 번의 호출로 Challenge 생성, 세션 연결, 이메일 전송이 모두 이루어집니다. 이메일을 보내기 위한 추가 호출은 필요 없으며, /challenge/send-email은 부모 초대 Challenge를 거부합니다.
challengeId를 세션과 함께 저장하세요. 웹훅과 상태 엔드포인트, 취소 호출에서 이 초대를 식별하는 값입니다.
진행 중인 초대의 challengeId를 보관하고 있는 동안에는 같은 세션에 대해 다른 이메일 주소로 초대를 보내지 마세요.
초대 이메일의 링크는 라이브 모드에서 14일, 테스트 모드에서는 기다리지 않고 만료를 확인할 수 있도록 7분 동안 유효합니다.
처리해야 할 오류
오류는 HTTP 400으로 반환되며, 응답 본문의 error 필드에 코드가 담깁니다.
| 코드 | 발생 상황 | 대응 |
|---|---|---|
FEATURE_DISABLED | Verified parent linking is not enabled. 조직에서 VPL이 꺼져 있음 | k-ID에 연락 |
FEATURE_DISABLED | Verified parent linking is not available for this player. 플레이어가 액세스 연령 미만이거나 세션에 GUARDIAN 관리 권한이 있음 | VPC로 대체. 이 세션에서는 초대 옵션을 노출하지 않음 |
NOT_FOUND | 세션이 존재하지 않음 | 저장해 둔 sessionId 확인 |
INVALID_INPUT | 세션의 관할권이 제품에 구성되어 있지 않음 | Compliance Studio에서 제품 구성 수정 |
INVALID_EMAIL | Session is not eligible for a new invite. 이미 부모가 연결되어 있음 | 현재 연결 상태를 표시 |
4단계: 차단하지 않고 기다리기
초대가 진행 중인 동안에도 제품을 완전히 사용할 수 있게 유지하세요. 플레이어는 이미 연령 게이트를 통과했고, 초대가 플레이어가 할 수 있는 일을 바꾸지는 않습니다.
부모가 흐름을 완료하면 k-ID가 웹훅 엔드포인트로 Challenge.StateChange를 보냅니다.
{
"eventType": "Challenge.StateChange",
"data": {
"id": "ae6d4729-af32-42ea-8ef2-ff46c7664802",
"productId": 12345,
"type": "CHALLENGE_PARENT_INVITE",
"status": "PASS",
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"approverEmail": "parent@example.com",
"kuid": "12b9fa0e-6d6d-4903-a1fc-f2233027b71d"
}
}
수신하면 다음을 수행하세요.
data.id와data.sessionId를 저장해 둔 플레이어 레코드와 대조합니다.kuid를 캐시합니다. 이는 이 세션에 부모가 연결되어 있음을 알려줍니다.- 부모가 연결되었음을 UI에 반영합니다.
초대가 취소되면 같은 이벤트가 status: FAIL로 발생하지만, FAIL 페이로드에는 sessionId가 없습니다. data.sessionId가 아니라 저장해 둔 challengeId를 data.id와 대조하세요. 그러지 않으면 핸들러가 모든 취소를 놓칩니다. 연결된 부모가 없는 상태로 처리하세요. 플레이어는 다시 초대를 보낼 수 있습니다.
부모가 끝내 수락하지 않으면 더 이상 이벤트가 오지 않고 아무것도 바뀌지 않습니다.
5단계: 필요할 때 현재 상태 확인하기
상태 전환의 기준은 웹훅이지만, 플레이어가 앱을 다시 열 때처럼 기다리지 않고 현재 상태가 필요할 때가 많습니다.
지금 부모가 연결되어 있나요
sessionId로 GET /session/get을 호출해 두 필드를 확인합니다.
hasApproverEmail은 부모가 연결되어 있는 동안true이고 그 외에는false입니다. 분기에 사용할 신호는 이쪽입니다.kuid는 세션이 연결된 아동 프로필을 식별합니다.
마지막 초대는 어떻게 되었나요
저장해 둔 challengeId로 GET /challenge/get-status를 호출합니다.
| 상태 | 의미 | 권장 UI |
|---|---|---|
PENDING | 초대가 생성되고 이메일도 전송되었지만 부모가 아직 열지 않음 | parentEmail을 함께 보여주며 초대를 대기 중으로 표시 |
IN_PROGRESS | 부모가 링크를 열고 흐름을 시작함 | parentEmail을 함께 보여주며 초대를 대기 중으로 표시 |
PASS | 부모가 완료했고 세션이 연결됨 | 부모가 연결되었다고 표시 |
FAIL | 초대가 취소되었거나 종료됨 | 연결된 부모가 없다고 표시하고 다시 초대를 제안 |
함께 사용하기
플레이어 프로필을 렌더링할 때는 다음과 같이 하세요.
/session/get을 호출합니다.hasApproverEmail이true이면 부모가 연결되었다고 표시하고 7단계의 연결 해제 옵션을 제공합니다.- 그렇지 않으면 가장 최근
challengeId로/challenge/get-status를 호출합니다.PENDING또는IN_PROGRESS: 초대를 대기 중으로 표시하고 취소를 제공합니다.PASS인데/session/get에는 아직 부모가 보이지 않음: 짧은 경합입니다. 대기 중으로 처리하고 잠시 후 다시 확인하세요.FAIL이거나 저장된challengeId가 없음: 초대 옵션을 표시합니다.
6단계: 진행 중인 초대 취소하기
부모가 아직 확인을 진행하는 중이라도 플레이어가 초대를 철회하려 한다면 POST /challenge/cancel-invite를 호출하세요.
POST /challenge/cancel-invite
{
"challengeId": "ae6d4729-af32-42ea-8ef2-ff46c7664802"
}
{ "cancelled": true }
Challenge는 failureReason: "cancelled-by-initiator"와 함께 FAIL로 바뀌고, status: FAIL인 Challenge.StateChange가 전달됩니다. 이미 확인 위젯을 보고 있던 부모에게는 취소된 상태가 표시됩니다.
이미 취소된 초대를 다시 취소해도 안전하며 cancelled: true가 다시 반환됩니다.
| 코드 | 발생 상황 |
|---|---|
NOT_FOUND | 이 제품에 해당 Challenge가 없음 |
INVALID_INPUT | Invalid challenge type. 이 엔드포인트로 취소할 수 있는 것은 부모 초대 Challenge뿐 |
INVALID_INPUT | Invite already accepted. 취소가 도착하기 전에 부모가 완료함 |
FEATURE_DISABLED | 조직에서 VPL이 꺼져 있음 |
FAIL 이후 세션은 곧바로 새 초대를 받을 수 있습니다.
7단계: 연결 해제 처리하기
어느 쪽이든 언제든지 관계를 끝낼 수 있습니다. 부모는 Family Connect에서 해제할 수 있고, 여러분은 제품 안에서 플레이어에게 해제 옵션을 제공할 수 있습니다.
연결이 해제되면 세션은 연결되지 않은 상태로 되돌아갑니다. kuid와 부모 연결이 지워지고, 부모가 설정한 얼로원스와 컨트롤도 제거됩니다. 세션 자체는 ACTIVE 상태로 남고 플레이어의 권한과 전송된 데이터도 그대로 유지됩니다. 플레이어는 같은 sessionId로 계속 이용합니다.
제품에서 연결 해제하기
POST /session/unlink-parent
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"locale": "en"
}
{
"unlinked": true,
"unlinkedAt": "2026-04-20T18:42:11Z"
}
unlinked는 이번 호출이나 이전 호출이 부모 연결을 끝낸 경우 true입니다. 애초에 연결된 부모가 없었다면 false이며 unlinkedAt은 생략됩니다. locale은 연결이 끝났음을 알리기 위해 k-ID가 부모에게 보내는 알림 이메일의 언어를 지정합니다. 생략하면 k-ID가 해당 부모를 이미 알고 있는 경우 그 부모의 저장된 언어를 사용하고, 그다음 제품의 기본 언어, 마지막으로 영어로 대체됩니다.
이 호출은 sessionId에 대해 멱등합니다. 연결이 이미 해제된 뒤에 다시 호출하면 200과 함께 원래의 unlinkedAt이 반환되며, 다시 호출한 시각이 아닙니다.
| 코드 | 발생 상황 |
|---|---|
NOT_FOUND | 세션이 존재하지 않음 |
INVALID_INPUT | 세션의 관할권이 제품에 구성되어 있지 않음 |
FEATURE_DISABLED | Unlink is only available for VPL-linked sessions. 세션이 VPL 경로가 아니거나 조직에서 VPL이 꺼져 있음 |
Session.Unlink 웹훅
k-ID는 어느 쪽에서든 연결이 끝날 때마다 Session.Unlink를 보냅니다.
{
"eventType": "Session.Unlink",
"data": {
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"productId": 12345,
"unlinkedBy": "player",
"unlinkedAt": "2026-04-20T18:42:11Z"
}
}
unlinkedBy는 여러분의 API를 통한 해제이면 player, 부모가 Family Connect에서 해제했으면 parent입니다.
수신하면 부모가 연결된 것으로 표시하지 마세요. 플레이어는 언제든 새 초대를 보낼 수 있습니다.
다음 단계는 무엇인가요?
검증된 부모 연결을 구현했다면, 다음 리소스로 더 깊이 살펴보세요.
- Verified parent linking (VPL): 이 가이드의 바탕이 되는 개념. 링크와 게이트의 차이, 그리고 VPL과 VPC가 상호 배타적인 이유
- 세션: 연결, 연결 해제, 재연결에 걸친
sessionId와kuid의 동작 Session.Unlink: 어느 한쪽이 연결을 해제할 때 알려주는 웹훅- 웹훅: 웹훅 구독, 전달, 서명 검증에 대한 전체 가이드
- 출시 전 체크리스트: 출시 전에 확인할 요건