CDK

埋め込みフロー

CDKは、単一のインターフェース内で完全な検証可能な保護者の同意(VPC)フローを処理するエンドツーエンドウィジェットを提供し、年齢ゲート、VPC、データ通知、権限、設定を1つのシームレスなエクスペリエンスでカバーします。

ネイティブアプリでは、ネイティブのUXをおすすめします

ネイティブのモバイルアプリ、コンソール、その他ブラウザ上で動作しない環境では、カスタムワークフローとCDK UXガイドラインに従って、年齢ゲートと同意のUXをネイティブに構築することをおすすめします。プレイヤーにとって最もシームレスで、ブランドに統合されたエクスペリエンスになります。

エンドツーエンドウィジェットと年齢ゲートウィジェットも、これらの環境で完全にサポートされています。

  • モバイル: 確認URLと同じシステムブラウザサーフェスでウィジェットURLを表示し、redirectUrlコールバックで結果を受け取ります。表示方法については、モバイルアプリガイドを参照してください。
  • コンソール: ウィジェットの短縮URLをQRコードとして表示してプレイヤーにスマートフォンでフローを完了してもらい、widgetIdを使ってサーバー側で結果を取得します。サーバーで結果を取得するを参照してください。

エンドツーエンドウィジェットとは?

エンドツーエンドウィジェットは、単一のインターフェースで完全なコンプライアンスフローを処理する包括的なソリューションで、年齢ゲート、VPC、データ通知、権限、設定を1つのシームレスなエクスペリエンスでカバーします。このウィジェットは、子供のデバイスまたは親自身のデバイスで親が使用でき、同意プロセスに最大の柔軟性を提供します。

ウィジェットURLの生成

/widget/generate-e2e-url APIを呼び出して、完全なVPCフローを処理するエンドツーエンドウィジェットURLを作成します。これにより、ユーザーが年齢収集と保護者の同意プロセスを完了するための一意のURLが返されます。

リクエストの例

POST /api/v1/widget/generate-e2e-url
Content-Type: application/json
Authorization: Bearer your-api-key

{
  "jurisdiction": "US-CA"
}

設定フラグ

オプションのflagsパラメータを使用すると、スキップするフローの部分をカスタマイズできます:

  • skipDataNotices: データ通知と同意収集をスキップ
  • skipVerification: 確認ステップをスキップ
  • skipPermissions: 権限管理をスキップ
  • skipPreferences: 設定をスキップ

プラットフォーム年齢シグナルの送信(オプション)

ゲームがすでにプラットフォーム(Apple iOS、Google Play、Xbox、Meta Horizon、または以前のk-ID確認)からの年齢データを持っている場合、platformAgeSignalとしてリクエストボディに含めてください。ウィジェットはこのシグナルを内部の年齢ゲートチェックに転送するため、確認済みシグナルが成人を示す場合は年齢ゲートをスキップでき、別の確認手順なしに確認済み年齢権限を満たし、シグナルとプレイヤーが自己申告した年齢の矛盾を検出できます。

POST /api/v1/widget/generate-e2e-url
Content-Type: application/json
Authorization: Bearer your-api-key

{
  "jurisdiction": "US-CA",
  "platformAgeSignal": {
    "name": "apple-ios",
    "ageLow": 18,
    "ageHigh": 25,
    "declarationType": "governmentIDChecked"
  }
}

サポートされているプラットフォームとフィールド形式については、プラットフォーム年齢シグナルを参照してください。

レスポンスの例

{
  "url": "https://family.k-id.com/widget?token=eyJhbGciOiJFUzM4NCIs...",
  "shortUrl": "https://family.k-id.com/s/2f6c1d3e-8a4b-4c5d-9e6f-7a8b9c0d1e2f?pid=42&s=qr",
  "widgetId": "2f6c1d3e-8a4b-4c5d-9e6f-7a8b9c0d1e2f"
}
  • url: 埋め込みまたは表示に使う、ウィジェットの完全なURL。
  • shortUrl: urlにリダイレクトする短縮URL。QRコードに適しています。
  • widgetId: このウィジェットフローのID。DOMイベントやリダイレクトを使わずに結果をサーバーで受け取る必要がある場合は、プレイヤーと紐づけて保存してください。サーバーで結果を取得するを参照してください。

ウィジェットURLの表示

ウィジェットURLはホストされたWebページです。アプリケーションに適したサーフェスで開いてください:

  • WebサイトまたはWebアプリ: iframeに埋め込む(以下の例を参照)、ポップアップウィンドウで開く、または全画面リダイレクトする。
  • モバイルアプリ: 最もブランドに統合されたエクスペリエンスのために、カスタムワークフローで年齢ゲートと同意のUXをネイティブに構築することをおすすめします。ウィジェットを使う場合は、ウィジェットURLをシステムブラウザサーフェス(AndroidではCustom Tabs、iOSではASWebAuthenticationSession)で開き、options.redirectUrlを渡して、結果をアプリに戻るディープリンクとして受け取ります。表示方法については、モバイルアプリクイックスタートを参照してください。
  • コンソール (Switch、PlayStation、Xbox): カスタムワークフローで年齢ゲートと同意のUXをネイティブに構築し、保護者やプレイヤーがスマートフォンで操作する必要があるときだけチャレンジURLをQRコードとして表示することをおすすめします。ウィジェットを使う場合は、コンソールのブラウザは通常制限されているか存在しないため、shortUrlをQRコードとして表示し、プレイヤーにスマートフォンでフローを完了してもらいます。スマートフォンからコンソールへはリダイレクトできないため、widgetIdを使ってサーバー側で結果を取得してください。サーバーで結果を取得するを参照してください。

ウィジェット内で利用可能な方法は、ホストに関係なく管轄区域の要件に自動的に適応します。

Webの例

<div id="vpc-container">
  <iframe 
    id="vpc-widget"
    src="WIDGET_URL" 
    width="100%" 
    height="600"
    frameborder="0"
    allow="camera;payment;publickey-credentials-get;publickey-credentials-create">
  </iframe>
</div>

モバイルサーフェスの場合は、/widget/generate-e2e-urlを呼び出すときにoptions.redirectUrlを渡し、アプリでコールバックを処理してください。エンドツーエンドの例については、モバイルアプリクイックスタートを参照してください。

イベントの処理

DOMイベントが届く場所

以下のJavaScriptイベント(Widget.AgeGate.Result、Widget.AgeGate.Challenge、Widget.ExitReview)はpostMessageで配信されます。受信するには、ウィジェットのwindowに対するライブJavaScriptリスナーがホスト側に必要です。これにはiframeホスト、window.openで開かれたポップアップウィンドウ、JSブリッジを持つモバイルWebView / WKWebViewが含まれます(モバイルアプリクイックスタートを参照)。システムブラウザコンポーネント(ASWebAuthenticationSession、SFSafariViewController、Chrome Custom Tabs)と全画面トップレベルリダイレクトはリスナーを公開しないため、redirectUrlコールバックで結果を受け取り、/session/getまたはWebhooksでサーバーサイドで確認してください。

年齢ゲートフローが完了すると、プレイヤーの権限と年齢ステータスを保存するためにセッションが作成されます。フローがチャレンジを必要とするたびにチャレンジが作成されます。これは、確認可能な保護者の同意、またはプレイヤーが保護者の同意をスキップできる年齢を申告した場合のAutomatic age assuranceのためです。

ウィジェットは、リッスンできるJavaScriptイベントを発行します。Widget.AgeGate.Resultイベントをリッスンしてください。そのdata.statusはフローがどのように終了したかを示します:

  • PASS: プレイヤーが年齢ゲートを通過し、セッションが作成されました。data.sessionIdが存在します。
  • FAIL: 必要なチャレンジが通過しませんでした(たとえば、信頼できる大人が保護者の同意を拒否した、またはプレイヤーがAutomatic age assuranceを完了しなかった場合)。セッションは作成されません。
  • PROHIBITED: プレイヤーはこの管轄区域におけるプロダクトの最低年齢に達しておらず、続行できません。セッションは作成されず、data.ageに入力された年齢が含まれます。プレイヤーの続行をブロックしてください。

フロー中にチャレンジが作成された場合、イベントにはchallengeIdも含まれます。チャレンジ固有のイベントの詳細については、Widget.AgeGate.Challengeを参照してください。

UIを閉じる

ウィジェットUIをいつ閉じるかを判断するには、Widget.ExitReviewイベントをリッスンしてください。このイベントは、ユーザーが「完了」ボタンをクリックしたときに発行され、フローが完了し、iframeを閉じるか非表示にする必要があることを示します。

window.addEventListener('message', (event) => {
  if (!event.origin.endsWith('.k-id.com')) {
    return;
  }

  const message = event.data;

  if (message.eventType === 'Widget.AgeGate.Result') {
    if (message.data.status === 'PASS') {
      const sessionId = message.data.sessionId;

      // challengeId が存在する場合、フロー中にチャレンジが解決されました
      // (たとえば、保護者の同意または Automatic age assurance)。
      if (message.data.challengeId) {
        console.log('Challenge resolved, session issued:', sessionId);
      } else {
        console.log('Session created (no challenge required):', sessionId);
      }

      grantAccess(sessionId);
    } else if (message.data.status === 'FAIL') {
      // 必要なチャレンジが通過しませんでした(たとえば、保護者の同意が
      // 拒否された、または Automatic age assurance が完了しなかった)。セッションなし。
      console.log('Age gate not passed');
      restrictAccess();
    } else if (message.data.status === 'PROHIBITED') {
      // プレイヤーはプロダクトの最低年齢に達していません。
      // セッションは作成されません。続行をブロックします。
      console.log('Player below minimum age:', message.data.age);
      restrictAccess();
    }
  }

  // 必要に応じてチャレンジ固有のイベントを処理
  if (message.eventType === 'Widget.AgeGate.Challenge') {
    if (message.data.status === 'FAIL') {
      // 保護者が同意を拒否。アクセスを制限
      console.log('Consent denied');
      restrictAccess();
    }
  }

  if (message.eventType === 'Widget.ExitReview') {
    // ユーザーが「完了」をクリックしたときにウィジェットUIを閉じる
    closeWidget();
  }
});

リダイレクトURLで結果を受け取る

システムブラウザサーフェス(ASWebAuthenticationSession、SFSafariViewController、Chrome Custom Tabs)と全画面トップレベルリダイレクトはDOMリスナーを公開できないため、代わりにredirectUrlコールバックで年齢ゲートの結果を受け取ります。/widget/generate-e2e-urlまたは/widget/generate-age-gate-urlを呼び出すときにoptions.redirectUrlを渡してください。このURLはhttp(s)とカスタムスキームのモバイルディープリンク(たとえばmyapp://age-gate/return)を受け付けます。

年齢ゲートフローが完了すると、k-IDはWidget.AgeGate.Resultイベントが持つのと同じ結果フィールドをクエリパラメータとして追加し、プレイヤーをあなたのURLに遷移させます。リダイレクトURLの既存のクエリパラメータは保持されます。

パラメータ説明
statusPASS、FAIL、またはPROHIBITED(以下を参照)
sessionId新しく作成されたセッション。statusがPASSの場合にのみ存在します。
challengeIdフローに関与したチャレンジ(作成された場合)。
ageプレイヤーが入力した年齢。statusがPROHIBITEDの場合に存在します。

statusの値はDOMイベントと一致します:

  • PASS: プレイヤーが年齢ゲートを通過し、セッションが作成されました。sessionIdでアクセスを許可します。
  • FAIL: 必要なチャレンジが通過しませんでした(たとえば、保護者の同意が拒否された、またはAutomatic age assuranceが完了しなかった場合)。セッションは作成されません。
  • PROHIBITED: プレイヤーはこの管轄区域におけるプロダクトの最低年齢に達していません。セッションは作成されません。プレイヤーの続行をブロックしてください。

たとえば、最低年齢に達していないプレイヤーは次のURLに遷移します:

myapp://age-gate/return?status=PROHIBITED&age=8

リダイレクトのクエリパラメータは信頼できる結果ではなく、UXのヒントとして扱ってください。アクセスを許可する前に、Challenge.StateChange webhookまたは/session/getでサーバーサイドで結果を確認してください。

サーバーで結果を取得する

コンソールに表示されたQRコードをスキャンしたスマートフォンなど、プレイヤーが別のデバイスでウィジェットを完了した場合、DOMイベントもリダイレクトもアプリケーションには届きません。代わりにサーバーがk-IDから結果を取得します。方法はウィジェットのステータスのポーリング、Webhook、またはその両方で、/widget/generate-e2e-urlと/widget/generate-age-gate-urlが返すwidgetIdをキーにします。その他のウィジェットエンドポイントはwidgetIdを返しません。

コンソールでは、ネイティブのUXをおすすめします

カスタムワークフローで年齢ゲートと同意のUXをネイティブに構築し、チャレンジが必要なときだけ、チャレンジURLをQRコードとして表示してスマートフォンに引き継ぐことをおすすめします。以下のウィジェットフローもコンソールで動作しますが、フロー全体をプレイヤーのスマートフォンで行うことになります。

  1. サーバーからウィジェットURLを生成し、QRコードを表示する前にwidgetIdをプレイヤーと紐づけて保存します。shortUrlは不透明な値として扱い、返されたとおりにエンコードしてください。
  2. GET /widget/get-status(一度に最大100件のウィジェットにはPOST /widget/get-status)をポーリングするか、Webhookで結果を取得します。Webhookで結果を受け取るを参照してください。
  3. PASSの場合は、返されたsessionIdを使って/session/getでセッションを取得し、結果を自社側に保存します。k-IDがウィジェットのステータスを保持するのは一定期間のみで、その後はNOT_FOUNDを返します。
ステータス意味対応
PENDINGプレイヤーはまだ年齢ゲートを完了していません。expiresAtはURLが無効になる日時です。QRコードを表示したまま、数秒ごとにポーリングします。
PASS試行が合格し、sessionIdが設定されています。一度PASSになったウィジェットは、その後もPASSを返し続けます。セッションを取得し、プレイヤーを先に進めます。
IN_CHALLENGE保護者の同意などのチャレンジが完了していません。challengeTypeとchallengeStatusを確認してください。誰が対応する必要があるかをプレイヤーに伝えます。保護者の対応には数日かかることがあるため、ポーリング間隔を長くするか、Webhookを利用してください。
FAILED保護者が拒否したなどの理由で、チャレンジが失敗しました。プレイヤーを先に進めません。
PROHIBITEDプレイヤーは先に進めません。prohibitedReasonはBELOW_MINIMUM_AGEまたはJURISDICTION_UNAVAILABLEです。プレイヤーを先に進めません。
EXPIRED試行前にURLの有効期限が切れたか、チャレンジに対応できなくなりました。ポーリングを停止します。

フローが終了すると(合格、禁止、失敗、期限切れ)、どのデバイスからでも同じQRコードでフローを再開することはできません。年齢の入力ミスなどでプレイヤーにもう一度試してもらうには、新しいウィジェットを生成してください。widgetIdはshortUrlの一部なので秘密の値ではありませんが、そのステータスを読み取れるのは生成したプロダクトだけです。他のプロダクトにはNOT_FOUNDが返されます。

Webhookで結果を受け取る

ポーリングの代わりにチャレンジの結果をプッシュで受け取るには、Challenge.StateChangeを購読してください。ウィジェットから開始したチャレンジではwidgetIdを含みます。最も多い結果であるチャレンジなしの合格と、禁止の結果にはWebhookが送信されないため、これらはポーリングで確認してください。

ウィジェットの結果の正となる情報源は/widget/get-statusです。Webhookも同じ結果を報告しますが、少なくとも1回の配信で取りこぼす可能性があるため、Webhookを受け取っていない場合はポーリングをフォールバックとして使用してください。

Safe Startで合格した後は、保護者が後から同意を拒否しても、ウィジェットはPASSを返し続けます。同意チャレンジについてはFAILのChallenge.StateChangeを受け取りますが、Safe Startのセッションは有効なままで、同意の状態はchallengeStatusで確認できます。

プラットフォーム年齢シグナル

年齢入力画面をスキップできるのは、検証済みのApple iOSまたはGoogle Playのシグナルだけです。XboxとMeta Horizonのシグナルはエラーなく受け付けられますが、プレイヤーはスマートフォンで年齢を入力します。

ウィジェットが処理する内容

ウィジェットは自動的に以下を処理します:

  • 年齢収集: 管轄区域に適した年齢収集方法
  • データ通知: Compliance Studioでのプロダクト設定に応じて受け入れるデータ通知
  • 権限: Compliance Studioでのプロダクト設定に応じて管理する権限
  • 保護者の同意チャレンジ: ユーザーが未成年者と判断された場合、信頼できる大人の承認のためにチャレンジが作成されます
  • Automatic age assurance: プロダクトが管轄区域で Automatic age assurance を有効化している場合、申告された年齢が保護者の同意をスキップできるほど高いプレイヤーは、セッションが発行される前にウィジェット内で申告した年齢を証明するよう求められます(顔年齢推定またはID書類)。

特定のフローは、管轄区域とCompliance Studioでのプロダクト設定によって異なります。

Automatic age assurance によるセッション作成タイミング

Automatic age assurance がエンドツーエンドウィジェット内でトリガーされる場合、セッションは年齢ゲート完了直後ではなく、プレイヤーが検証を通過した後に作成されます。フロー完了時に Widget.AgeGate.Result イベントは引き続き発火し、PASS の場合は sessionId を含みます。イベントが届くまでは、フローは進行中として扱ってください。

VPCの実装の詳細については、クイックスタートガイドを参照してください。

On this page