커스텀 연령 게이트
이 가이드는 사전 구축된 위젯을 사용하지 않고 k-ID API를 직접 사용하여 커스텀 연령 게이트를 구현하는 방법을 안내합니다. 이 접근 방식은 k-ID가 규정 준수 로직을 처리하는 동안 사용자 인터페이스를 완전히 제어할 수 있게 해줍니다.
이 가이드는 Compliance Studio에서 모든 권한을 필수로 구성하는 간소화된 통합 패턴을 보여줍니다. 이 구성에서는 세션이 생성되면(직접 또는 부모 동의 후) 모든 기능을 즉시 사용할 수 있으며, 세션 업그레이드 플로우가 필요하지 않습니다.
제품이 대상 관할권에 Automatic age assurance가 활성화된 경우, 모든 권한이 필수로 구성되어 있더라도, 신고된 연령이 부모 동의를 건너뛸 만큼 높은 플레이어에 대해 /age-gate/check가 CHALLENGE_AGE_GATE_AGE_ASSURANCE 챌린지를 반환할 수 있습니다. 플레이어가 셀프 검증을 완료할 때까지 세션 생성이 지연됩니다. 이 기능은 k-ID만 부여할 수 있는 조직 수준 설정에 의해 제어됩니다. 제품에 활성화되어 있지 않다면 4b단계를 건너뛸 수 있습니다.
커스텀 연령 게이트란?
커스텀 연령 게이트는 사용자가 구축하고 제어하는 연령 확인 인터페이스이며, k-ID의 API가 백그라운드에서 규정 준수 로직을 처리합니다. 이 접근 방식은 다음과 같은 경우에 이상적입니다:
- 완전한 UI 제어: 브랜드와 사용자 경험에 맞는 연령 게이트 설계
- 플랫폼 통합: 모바일 앱이나 게임 엔진을 위한 네이티브 경험 구축
- 커스텀 워크플로우: 더 큰 온보딩 플로우의 일부로 연령 확인 구현
사전 요구 사항
시작하기 전에 다음이 필요합니다:
- k-ID 제품: k-ID Compliance Studio에서 제품을 생성하고 구성
- 필수 권한 구성: 제품의 권한 설정에서 모든 권한을 "필수"로 구성
- API 키: Compliance Studio의 제품 개발자 설정 페이지에서 API 키 생성
- Webhook 엔드포인트(권장): 챌린지 및 세션 이벤트를 수신하기 위한 보안 HTTPS 엔드포인트 설정. 자세한 내용은 Webhooks 참조.
1단계: 연령 게이트 요구 사항 가져오기
연령 게이트를 표시하기 전에 /age-gate/get-requirements를 호출하여 사용자의 관할권에 따라 표시해야 할 내용을 결정합니다.
구현에서 모든 API 호출은 API 키가 클라이언트 측 코드에 노출되지 않도록 서버 간 호출이어야 합니다.
요청 예시
GET /api/v1/age-gate/get-requirements?jurisdiction=US-CA
Authorization: Bearer your-api-key
응답 예시
{
"shouldDisplay": true,
"ageAssuranceRequired": false,
"digitalConsentAge": 13,
"civilAge": 18,
"minimumAge": 0,
"approvedAgeCollectionMethods": [
"date-of-birth",
"age-slider",
"platform-account"
]
}
응답 필드
| 필드 | 설명 |
|---|---|
shouldDisplay | 연령 게이트를 표시해야 하는지 여부 |
ageAssuranceRequired | 이 관할권에서 연령 확인이 필요한지 여부 |
digitalConsentAge | 디지털 동의를 위한 최소 연령(이 연령 미만의 사용자는 부모 동의 필요) |
civilAge | 사용자가 법적 성인으로 간주되는 연령 |
minimumAge | 제품에 접근하기 위한 최소 연령(Compliance Studio에서 구성) |
approvedAgeCollectionMethods | 이 관할권에서 허용되는 연령 수집 방법 |
shouldDisplay가 false인 경우, 연령 게이트를 건너뛰고 /age-gate/get-default-permissions를 호출하여 해당 관할권의 기본 세션 권한을 가져옵니다.
게임에 플랫폼이 보고한 연령 신호(Apple iOS, Google Play, Xbox, Meta Horizon 또는 k-ID)가 있는 경우 쿼리 매개변수(platformName, platformAgeLow, platformAgeHigh, platformCategory, platformDeclarationType, platformVerificationId)로 포함하면, 확인된 성인 신호가 shouldDisplay를 false로 전환하여 연령 게이트를 완전히 건너뛸 수 있습니다. 플랫폼 연령 신호를 참조하세요.
2단계: 연령 게이트 UI 구축
응답에 따라 승인된 수집 방법을 사용하여 연령 게이트 인터페이스를 구축합니다:
date-of-birth: 전체 생년월일 입력(YYYY-MM-DD)age-slider: 연령 범위 또는 슬라이더 선택platform-account: 기존 플랫폼 계정 연령 데이터 사용
연령 슬라이더 동작, 날짜 선택기 요구 사항, 접근성 고려 사항을 포함한 자세한 디자인 권장 사항은 UX 가이드라인을 참조하세요.
연령 게이트 UI 예시
<div id="age-gate">
<h2>생년월일을 입력해 주세요</h2>
<form id="age-gate-form">
<label for="dob">생년월일</label>
<input type="date" id="dob" name="dob" required />
<button type="submit">계속</button>
</form>
</div>
3단계: API로 연령 확인
사용자가 연령을 제출하면 수집한 연령 정보와 관할권을 사용하여 /age-gate/check를 호출합니다. 사용한 연령 수집 방법에 따라 dateOfBirth 또는 age를 전달할 수 있습니다.
생년월일을 사용한 요청 예시
날짜 선택기를 사용하여 전체 생년월일을 수집한 경우:
POST /api/v1/age-gate/check
Content-Type: application/json
Authorization: Bearer your-api-key
{
"jurisdiction": "US-CA",
"dateOfBirth": "2015-04-15"
}
연령을 사용한 요청 예시
연령 슬라이더를 사용하여 사용자의 연령을 수집한 경우:
POST /api/v1/age-gate/check
Content-Type: application/json
Authorization: Bearer your-api-key
{
"jurisdiction": "US-CA",
"age": 9
}
플랫폼 연령 신호를 사용한 요청 예시
platformAgeSignal을 단독으로 또는 dateOfBirth/age와 함께 포함할 수도 있습니다. 확인된 신호는 추가 확인 단계 없이 확인된 연령 권한을 충족하고, 확인되지 않은 신호는 여전히 연령 충돌 감지에 사용됩니다. 플랫폼 연령 신호를 참조하세요.
POST /api/v1/age-gate/check
Content-Type: application/json
Authorization: Bearer your-api-key
{
"jurisdiction": "US-CA",
"dateOfBirth": "2005-04-15",
"platformAgeSignal": {
"name": "apple-ios",
"ageLow": 18,
"ageHigh": 25,
"declarationType": "governmentIDChecked"
}
}
가능한 응답
API는 세 가지 상태 중 하나를 반환합니다: PASS, PROHIBITED, CHALLENGE. PASS인 경우 즉시 세션이 생성됩니다. CHALLENGE 응답은 세션이 존재하기 전에 해결되어야 하며, challenge.type에 따라 분기하여 표시 방법을 결정해야 합니다:
CHALLENGE_PARENTAL_CONSENT: 신고된 연령이 너무 어려서 부모 동의 없이 진행할 수 없습니다. 신뢰할 수 있는 성인이 승인해야 합니다(4단계).CHALLENGE_AGE_GATE_AGE_ASSURANCE: 신고된 연령은 부모 동의를 건너뛸 만큼 높지만, 제품이 이 관할권에 대해 Automatic age assurance를 활성화하여 플레이어가 신고를 증명해야 합니다(4b단계).
PASS: 사용자가 진행할 수 있음
사용자의 연령으로 즉시 접근이 허용됩니다. 모든 권한이 있는 세션이 생성됩니다.
{
"status": "PASS",
"session": {
"sessionId": "608616da-4fd2-4742-82bf-ec1d4ffd8187",
"ageStatus": "LEGAL_ADULT",
"dateOfBirth": "2005-04-15",
"jurisdiction": "US-CA",
"permissions": [...],
"status": "ACTIVE"
}
}
조치: sessionId를 저장하고 사용자가 진행할 수 있도록 합니다.
PROHIBITED: 사용자가 차단됨
사용자의 연령이 제품에 구성된 최소 연령보다 낮습니다.
{
"status": "PROHIBITED"
}
조치: 연령에 적합한 메시지를 표시하고 접근을 방지합니다.
CHALLENGE 와 CHALLENGE_PARENTAL_CONSENT: 부모 동의 필요
사용자의 연령에 검증 가능한 부모 동의(VPC)가 필요합니다. 신뢰할 수 있는 성인이 승인할 수 있는 챌린지가 생성됩니다.
{
"status": "CHALLENGE",
"challenge": {
"challengeId": "683409f1-2930-4132-89ad-827462eed9af",
"oneTimePassword": "PP5BUS",
"type": "CHALLENGE_PARENTAL_CONSENT",
"url": "https://family.k-id.com/authorize?otp=PP5BUS"
}
}
조치: challengeId를 저장하고 신뢰할 수 있는 성인 챌린지 화면을 표시합니다(4단계 참조).
CHALLENGE 와 CHALLENGE_AGE_GATE_AGE_ASSURANCE: Automatic age assurance
플레이어가 부모 동의를 건너뛸 만큼 높은 연령을 신고하고 제품이 해당 관할권에 대해 Automatic age assurance를 활성화한 경우에 반환됩니다. 세션이 생성되기 전에 플레이어가 신고를 증명해야 합니다(얼굴 연령 추정 또는 ID 문서). 신뢰할 수 있는 성인이 관여하지 않으며 OTP가 발급되지 않습니다.
{
"status": "CHALLENGE",
"challenge": {
"challengeId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "CHALLENGE_AGE_GATE_AGE_ASSURANCE",
"url": "https://family.k-id.com/age-gate/verify?token=..."
}
}
조치: challengeId를 저장하고 challenge.url을 iframe에 임베드합니다(4b단계 참조).
4단계: 신뢰할 수 있는 성인 챌린지 화면 구축
CHALLENGE 응답을 받으면 사용자가 동의를 위해 신뢰할 수 있는 성인에게 연락할 수 있는 화면을 표시합니다. 챌린지 응답에는 필요한 모든 것이 포함되어 있습니다.
레이아웃 예시 및 구현 팁을 포함한 신뢰할 수 있는 성인 동의 흐름에 대한 자세한 디자인 권장 사항은 UX 가이드라인의 보호자 동의를 참조하세요.
챌린지 화면 옵션
챌린지 화면은 신뢰할 수 있는 성인이 동의를 완료할 수 있는 세 가지 옵션을 제공해야 합니다:
옵션 1: 이메일 알림
사용자가 신뢰할 수 있는 성인의 이메일 주소를 입력할 수 있도록 합니다. 제출되면 /challenge/send-email을 호출하여 동의 요청 이메일을 보냅니다.
POST /api/v1/challenge/send-email
Content-Type: application/json
Authorization: Bearer your-api-key
{
"challengeId": "683409f1-2930-4132-89ad-827462eed9af",
"email": "parent@example.com"
}
옵션 2: QR 코드
challenge.url 필드에서 생성된 QR 코드를 표시합니다. 신뢰할 수 있는 성인은 휴대폰으로 이를 스캔하여 동의 포털에 직접 접근할 수 있습니다.
// 챌린지 URL에서 QR 코드 생성
const qrCodeUrl = challenge.url;
// QR 코드 라이브러리를 사용하여 렌더링: "https://family.k-id.com/authorize?otp=PP5BUS"
옵션 3: 수동 코드 입력
challenge.oneTimePassword를 표시하고 신뢰할 수 있는 성인에게 asktoplay.com을 방문하여 코드를 입력하도록 안내합니다.
챌린지 화면 구현 예시
<div id="challenge-screen">
<h2>신뢰할 수 있는 성인에게 도움을 요청하세요</h2>
<p>
신뢰할 수 있는 성인에게 다음 방법 중 하나를 사용하여 설정을 완료해 달라고
요청하세요.
</p>
<div class="options-container">
<!-- 옵션 1: 이메일 -->
<div class="option">
<h3>옵션 1</h3>
<p>신뢰할 수 있는 성인의 이메일 입력</p>
<form id="email-form">
<label for="email">이메일</label>
<input type="email" id="email" placeholder="이메일 주소" required />
<button type="submit">제출</button>
</form>
</div>
<!-- 옵션 2: QR 코드 -->
<div class="option">
<h3>옵션 2</h3>
<p>신뢰할 수 있는 성인에게 QR 코드를 스캔하거나 클릭하도록 요청하세요.</p>
<div id="qr-code">
<!-- challenge.url에서 QR 코드 렌더링 -->
</div>
</div>
<!-- 옵션 3: 수동 코드 -->
<div class="option">
<h3>옵션 3</h3>
<p>
<a href="https://asktoplay.com">asktoplay.com</a>으로 이동하여 아래
코드를 입력하세요.
</p>
<div class="code-display">
<span id="otp-code">PP5BUS</span>
<button onclick="copyCode()">복사</button>
</div>
</div>
</div>
<button id="do-later">나중에 하기</button>
</div>
챌린지 저장
동의를 기다리는 동안 challengeId를 저장합니다. 로컬 스토리지, 사용자 계정과 연결된 데이터베이스 또는 플랫폼에 적합한 다른 영구 저장소에 저장할 수 있습니다. 동의가 부여되기 전에 사용자가 앱으로 돌아오면 /challenge/get을 사용하여 챌린지를 검색하고 챌린지 화면을 복원합니다.
// 챌린지 생성 시 저장 (localStorage를 사용한 예시)
localStorage.setItem("pendingChallenge", challengeId);
// 앱 재시작 시 보류 중인 챌린지 확인
const pendingChallenge = localStorage.getItem("pendingChallenge");
if (pendingChallenge) {
// 챌린지 세부 정보를 가져와 챌린지 화면 표시
const challenge = await fetchChallenge(pendingChallenge);
showChallengeScreen(challenge);
}
4b단계: 자동 연령 보증 챌린지 처리
제품에 Automatic age assurance가 활성화되지 않은 경우 이 단계를 건너뛰세요. challenge.type이 CHALLENGE_AGE_GATE_AGE_ASSURANCE인 경우 플레이어가 직접 검증합니다. 4단계의 신뢰할 수 있는 성인 화면을 표시하지 마세요.
검증 iframe 임베드
challenge.url을 iframe에 직접 렌더링합니다. iframe은 관할권 및 제품 구성에 따라 얼굴 연령 추정 또는 ID 문서 검증을 처리합니다.
<iframe
id="age-assurance-widget"
src="CHALLENGE_URL"
width="100%"
height="600"
frameborder="0"
allow="camera;payment;publickey-credentials-get;publickey-credentials-create">
</iframe>
allow 속성은 필수입니다:
camera: 얼굴 연령 추정payment: 신용카드 기반 검증publickey-credentials-get/publickey-credentials-create: WebAuthn / AgeKey
Verification.Result DOM 이벤트 수신
플레이어가 완료하면 iframe이 Verification.Result 메시지를 보냅니다. 이는 반응형 UI 업데이트에만 사용하세요. 데이터 무결성을 위해서는 5단계의 webhook을 기다리거나 /challenge/get-status를 폴링하세요.
window.addEventListener("message", (event) => {
if (!event.origin.endsWith(".k-id.com")) {
return;
}
const message = event.data;
if (message?.eventType === "Verification.Result") {
if (message.data.status === "PASS") {
// 플레이어가 성공적으로 검증되었습니다.
// 세션이 생성 중입니다 - sessionId를 가져오려면
// Challenge.StateChange webhook을 기다리거나 /challenge/get-status를 폴링하세요.
closeAgeAssuranceIframe();
} else if (message.data.status === "FAIL") {
// 플레이어가 연령 기준을 충족하지 않았습니다. 세션이 생성되지 않습니다.
closeAgeAssuranceIframe();
showVerificationFailedMessage();
}
}
});
자세한 이벤트 구조는 Verification.Result를 참조하세요.
CHALLENGE_PARENTAL_CONSENT와의 차이점
| 항목 | CHALLENGE_PARENTAL_CONSENT(4단계) | CHALLENGE_AGE_GATE_AGE_ASSURANCE(4b단계) |
|---|---|---|
| 검증하는 사람 | 신뢰할 수 있는 성인 | 플레이어 본인 |
oneTimePassword | 존재 | 존재하지 않음 |
challenge.url | https://family.k-id.com/authorize?otp=... | https://family.k-id.com/age-gate/verify?token=... |
| UI | 이메일 / QR 코드 / OTP 입력 | 카메라 액세스가 있는 iframe |
/challenge/send-email | 적용 가능 | 적용 불가 |
| 세션 생성 시점 | 성인이 승인할 때 생성 | 플레이어가 검증을 통과한 후 생성 |
5단계: Webhook 이벤트 처리
챌린지 상태가 변경되거나 세션이 삭제될 때 이벤트를 수신하도록 webhook 엔드포인트를 구성합니다.
Challenge.StateChange 이벤트
이 이벤트는 부모 동의 챌린지든 자동 연령 보증 챌린지든, 챌린지가 해결될 때 발생합니다. 챌린지가 생성된 경우에만(즉, /age-gate/check 응답이 status: "CHALLENGE"였을 때) 전송됩니다.
{
"eventType": "Challenge.StateChange",
"data": {
"id": "683409f1-2930-4132-89ad-827462eed9af",
"productId": 42,
"status": "PASS",
"dob": "2015-04-15",
"sessionId": "0ad1641f-c154-4c2-8bb2-74dbd0de7723",
"approverEmail": "parent@example.com",
"kuid": "7a1f2c3d-4e5f-6789-abcd-ef0123456789"
}
}
approverEmail, dob, kuid 등의 필드는 선택 사항입니다. 받게 되는 필드 집합은 해당 챌린지를 생성한 흐름에 따라 다릅니다. challengeId를 사용하여 이벤트를 발급한 챌린지에 연결하세요. 전체 필드 계약은 Challenge.StateChange를 참조하세요.
| 상태 | 설명 | 조치 |
|---|---|---|
PASS | 동의가 부여됨 | sessionId를 저장하고 접근 허용 |
FAIL | 동의가 거부됨 | 적절한 메시지 표시, 사용자 진행 불가 |
IN_PROGRESS | 챌린지가 여전히 보류 중 | 계속 대기 |
성공적인 동의 처리
PASS 상태를 받으면(웹훅 또는 폴링을 통해) 응답에 sessionId가 포함됩니다. 다음을 수행해야 합니다:
- 세션 권한 가져오기:
sessionId를 사용하여/session/get을 호출하여 권한을 포함한 전체 세션 세부 정보를 검색합니다.
GET /api/v1/session/get?id=0ad1641f-c154-4c2-8bb2-74dbd0de7723
Authorization: Bearer your-api-key
응답 예시:
{
"session": {
"ageStatus": "DIGITAL_MINOR",
"dateOfBirth": "2015-04-15",
"etag": "6d9d24fccd428f845b355122799948dd0a52fc5d",
"jurisdiction": "US-CA",
"kuid": "7a1f2c3d-4e5f-6789-abcd-ef0123456789",
"permissions": [
{
"enabled": true,
"managedBy": "GUARDIAN",
"name": "text-chat-private"
},
{
"enabled": false,
"managedBy": "GUARDIAN",
"name": "voice-chat"
}
],
"sessionId": "0ad1641f-c154-4c2-8bb2-74dbd0de7723",
"status": "ACTIVE"
},
"status": "PASS"
}
- 저장된 챌린지를 세션으로 교체:
challengeId를 지우고 대신sessionId를 저장합니다. 이는 사용자가 이제 권한이 부여된 활성 세션을 가지고 있음을 나타냅니다.
// 보류 중인 챌린지를 지우고 활성 세션 저장
user.challengeId = null;
user.sessionId = data.sessionId;
- 권한 적용: 세션 응답의 권한을 사용하여 애플리케이션에서 기능을 활성화하거나 비활성화합니다.
Session.Delete 이벤트
이 이벤트는 세션이 삭제될 때(예: 부모가 Family Connect를 통해 접근을 취소할 때) 발생합니다.
{
"eventType": "Session.Delete",
"data": {
"id": "0ad1641f-c154-4c2-8bb2-74dbd0de7723",
"productId": 42
}
}
조치: 저장된 세션을 제거하고 사용자가 연령 게이트 플로우를 다시 완료하도록 요구합니다.
Webhook 핸들러 예시
app.post("/webhook/k-id", (req, res) => {
const { eventType, data } = req.body;
switch (eventType) {
case "Challenge.StateChange":
if (data.status === "PASS") {
// 접근 허용 - 사용자의 sessionId 저장
grantAccess(data.sessionId, data.kuid);
} else if (data.status === "FAIL") {
// 접근 거부
denyAccess(data.id);
}
break;
case "Session.Delete":
// 접근 취소 - 사용자가 연령 게이트를 다시 완료해야 함
revokeSession(data.id);
break;
}
res.status(200).send("OK");
});
폴링을 대체 수단으로 사용
Webhook을 사용할 수 없는 경우 /challenge/get-status를 폴링하여 챌린지 상태를 확인할 수 있습니다:
GET /api/v1/challenge/get-status?id=683409f1-2930-4132-89ad-827462eed9af
Authorization: Bearer your-api-key
폴링할 때 요청 사이에 최소 5초를 기다려야 합니다. 너무 자주 폴링하면 API가 HTTP 429를 반환할 수 있습니다.
Webhook 구성
커스텀 연령 게이트 플로우의 경우 webhook 엔드포인트가 다음을 수신하도록 구성되어 있는지 확인하세요:
Challenge.StateChange: 부모 동의가 승인되거나 거부될 때 알림Session.Delete: 부모가 세션을 삭제할 때 알림
Webhook 서명 확인에 대한 정보는 Webhooks를 참조하세요.
다음 단계
이제 커스텀 연령 게이트를 구현했으므로 다음 리소스를 탐색하여 통합을 강화하세요:
- UX 가이드라인: 연령 슬라이더, 날짜 선택기, 동의 흐름에 대한 디자인 권장 사항
- API 참조 문서: 연령 게이트 및 챌린지 API의 상세 문서
- Webhooks 설정: 프로덕션 시스템을 위한 강력한 webhook 처리 구현
- 모범 사례: 보안과 안정적인 사용자 경험을 보장하기 위한 모범 사례 구현
- 테스트: 테스트 모드 API로 통합 테스트
- 출시 전 체크리스트: 출시 전 요구 사항 검토
k-ID의 커스텀 연령 게이트 통합을 통해 k-ID가 복잡한 관할권 로직과 부모 동의 플로우를 처리하는 동안 완전히 브랜드화된 규정 준수 경험을 구축할 수 있습니다.