CDK

Embedded flow

The CDK provides an End-to-End widget that handles the complete Verifiable Parental Consent (VPC) flow within a single interface, covering age gate, VPC, data notices, permissions, and preferences all in one seamless experience.

In native apps, we recommend a native UX

In native mobile apps, consoles, and other surfaces that don't run in a browser, we recommend building the age gate and consent UX natively with the custom workflow and the CDK UX guidelines. It gives players the most seamless, brand-integrated experience.

The end-to-end and age gate widgets are fully supported there too:

  • Mobile: display the widget URL with the same system browser surfaces used for verification URLs, and receive the result through the redirectUrl callback. See the mobile apps guide for the display methods.
  • Console: display the widget's short URL as a QR code so the player finishes on their phone, and get the result on your server by widgetId. See Getting the result on your server.

What's the end-to-end widget?

The End-to-End Widget is a comprehensive solution that handles the complete compliance flow in a single interface, covering age gate, VPC, data notices, permissions, and preferences all in one seamless experience. This widget can be used by parents either on the child's device or on their own device, providing maximum flexibility for the consent process.

Generating the widget URL

Call the /widget/generate-e2e-url API to create an end-to-end widget URL that handles the complete VPC flow. This returns a unique URL for users to complete the age collection and parental consent process.

Example request

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

{
  "jurisdiction": "US-CA"
}

Configuration flags

The optional flags parameter allows you to customize which parts of the flow to skip:

  • skipDataNotices: Skip data notices and consent collection
  • skipVerification: Skip verification step
  • skipPermissions: Skip permission management
  • skipPreferences: Skip preference settings

Pass a platform age signal (optional)

If your game already has age data from the platform (Apple iOS, Google Play, Xbox, Meta Horizon, or a prior k-ID verification), include it as platformAgeSignal in the request body. The widget forwards the signal to the underlying age-gate check so it can skip the age gate when a verified signal indicates an adult, satisfy verified-age permissions without an extra verification step, and detect conflicts between the signal and the player's self-reported age.

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"
  }
}

For the supported platforms and field shapes, see Platform age signals.

Example response

{
  "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: the full widget URL, to embed or open.
  • shortUrl: a short URL that redirects to url, suited to a QR code.
  • widgetId: the ID of this widget flow. Store it against your player if the result has to reach your server without a DOM event or a redirect. See Getting the result on your server.

Presenting the widget URL

The widget URL is a hosted web page. Open it in the surface that fits your application:

  • Website or web app: embed in an iframe (example below), open as a pop-up, or redirect to it as a full page.
  • Mobile app: we recommend building the age gate and consent UX natively with the custom workflow for the most brand-integrated experience. To use the widget, open its URL in a system browser surface (Custom Tabs on Android, ASWebAuthenticationSession on iOS) and pass options.redirectUrl to receive the result as a deep link back into your app. See the Mobile apps quick start for the display methods.
  • Console (Switch, PlayStation, Xbox): we recommend building the age gate and consent UX natively with the custom workflow, showing only a challenge URL as a QR code when a parent or the player needs to act on a phone. To use the widget, display shortUrl as a QR code so the player completes the flow on their phone, since console browsers are typically restricted or absent. The phone can't redirect back to the console, so get the result on your server by widgetId. See Getting the result on your server.

The available methods inside the widget automatically adapt to jurisdictional requirements regardless of host.

Web example

<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>

For mobile surfaces, pass options.redirectUrl when calling /widget/generate-e2e-url and handle the callback in your app. See the Mobile apps quick start for end-to-end examples.

Handling events

Where DOM events reach you

The JavaScript events below (Widget.AgeGate.Result, Widget.AgeGate.Challenge, Widget.ExitReview) are delivered via postMessage. To receive them, your app needs a live JavaScript listener for the widget window. That covers iframes, pop-ups opened via window.open, and mobile WebView / WKWebView with a JS bridge (see the Mobile apps quick start). System browser components (ASWebAuthenticationSession, SFSafariViewController, Chrome Custom Tabs) and full-page top-level redirects don't expose a listener, so they receive results via the redirectUrl callback and confirm server-side with /session/get or webhooks.

When the age gate flow completes, a session is created to store the player's permissions and age status. A challenge is created whenever the flow needs one, either for Verifiable Parental Consent or for Automatic age assurance when the player claims an age old enough to skip parental consent.

The widget emits JavaScript events that you can listen to. Listen for the Widget.AgeGate.Result event, whose data.status reports how the flow ended:

  • PASS: the player cleared the age gate and a session was created. data.sessionId is present.
  • FAIL: a required challenge didn't pass (for example, a trusted adult denied parental consent, or the player didn't complete Automatic age assurance). No session is created.
  • PROHIBITED: the player is below the minimum age for the product in this jurisdiction and can't proceed. No session is created; data.age carries the entered age. Block the player from continuing.

If a challenge was created during the flow, the event also includes the challengeId. For detailed information about challenge-specific events, see Widget.AgeGate.Challenge.

Closing the UI

Listen for the Widget.ExitReview event to determine when to close the widget UI. This event is emitted when the user clicks the 'Done' button, indicating the flow is complete and the iframe should be closed or hidden.

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;

      // If challengeId is present, a challenge was resolved during the flow
      // (for example, parental consent or auto 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') {
      // A required challenge did not pass (for example, parental consent was
      // denied or Automatic age assurance was not completed). No session.
      console.log('Age gate not passed');
      restrictAccess();
    } else if (message.data.status === 'PROHIBITED') {
      // The player is below the minimum age for the product. No session is
      // created - block them from continuing.
      console.log('Player below minimum age:', message.data.age);
      restrictAccess();
    }
  }
  
  // Handle challenge-specific events if needed
  if (message.eventType === 'Widget.AgeGate.Challenge') {
    if (message.data.status === 'FAIL') {
      // Parent denied consent - restrict access
      console.log('Consent denied');
      restrictAccess();
    }
  }
  
  if (message.eventType === 'Widget.ExitReview') {
    // Close the widget UI when the user clicks 'Done'
    closeWidget();
  }
});

Receiving the result on the redirect URL

System browser surfaces (ASWebAuthenticationSession, SFSafariViewController, Chrome Custom Tabs) and full-page top-level redirects can't expose a DOM listener, so they receive the age gate result through the redirectUrl callback instead. Pass options.redirectUrl when calling /widget/generate-e2e-url or /widget/generate-age-gate-url. The URL accepts http(s) and custom-scheme mobile deeplinks (for example myapp://age-gate/return).

When the age gate flow completes, k-ID appends the same result fields carried by the Widget.AgeGate.Result event as query parameters and navigates the player to your URL. Existing query parameters on the redirect URL are preserved.

ParameterDescription
statusPASS, FAIL, or PROHIBITED (see below)
sessionIdThe newly created session. Present only when status is PASS.
challengeIdThe challenge involved in the flow, when one was created.
ageThe age the player entered. Present when status is PROHIBITED.

The status values match the DOM event:

  • PASS: the player cleared the age gate and a session was created. Grant access with sessionId.
  • FAIL: a required challenge didn't pass (for example, parental consent was denied or Automatic age assurance wasn't completed). No session is created.
  • PROHIBITED: the player is below the minimum age for the product in this jurisdiction. No session is created; block the player from continuing.

For example, a player who is below the minimum age lands on:

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

Treat the redirect query parameters as a UX hint, not a trusted result. Confirm the outcome server-side via the Challenge.StateChange webhook or /session/get before granting access.

Getting the result on your server

When the player finishes the widget on another device, such as a phone that scanned a QR code shown on a console, neither a DOM event nor a redirect can reach your application. Your server gets the outcome from k-ID instead, by polling the widget's status, by webhook, or both, keyed by the widgetId that /widget/generate-e2e-url and /widget/generate-age-gate-url return. The other widget endpoints don't return a widgetId.

On console, we recommend a native UX

We recommend building the age gate and consent UX natively with the custom workflow, and handing off to the phone only when a challenge needs it, by showing the challenge URL as a QR code. The widget flow below works on console too, but it sends the player to their phone for the whole flow.

  1. Generate the widget URL from your server and store widgetId against your player before you show the QR code. Treat shortUrl as opaque and encode it exactly as returned.
  2. Get the outcome by polling GET /widget/get-status (or POST /widget/get-status for up to 100 widgets at once), or by webhook. See Receiving the result by webhook.
  3. On PASS, read the session with /session/get using the reported sessionId, and store the outcome on your side. k-ID keeps a widget's status for a limited time, after which it returns NOT_FOUND.
StatusMeaningWhat to do
PENDINGThe player hasn't finished the age gate yet. expiresAt says when the URL stops working.Keep showing the QR code and poll every few seconds.
PASSAn attempt passed and sessionId is set. Once a widget reports PASS, it keeps reporting PASS.Read the session and let the player in.
IN_CHALLENGEA challenge such as parental consent hasn't resolved. See challengeType and challengeStatus.Tell the player who needs to act. A parent can take days, so poll slowly or rely on webhooks.
FAILEDThe challenge failed, for example because a parent declined.Keep the player out.
PROHIBITEDThe player can't proceed. prohibitedReason is BELOW_MINIMUM_AGE or JURISDICTION_UNAVAILABLE.Keep the player out.
EXPIREDThe URL expired before any attempt, or the challenge can no longer be acted on.Stop polling.

Once a flow has ended (passed, prohibited, failed, or expired), the same QR code can't start it again, on any device. To let the player try again, for example after a mistyped age, generate a new widget. A widgetId isn't a secret, since it's part of shortUrl, but only the product that generated it can read its status; any other product gets NOT_FOUND.

Receiving the result by webhook

To have challenge outcomes pushed instead of polling, subscribe to Challenge.StateChange, which carries widgetId for challenges started from a widget. A pass with no challenge, the most common outcome, and a prohibited result send no webhook, so keep polling for those.

/widget/get-status is the source of truth for a widget's outcome. Webhooks report the same outcomes, but they're at-least-once and can be missed, so use polling as a fallback whenever you haven't received one.

After a Safe Start pass, the widget keeps reporting PASS even if the parent later declines consent. You receive a Challenge.StateChange with FAIL for the consent challenge, but the Safe Start session stays active, and challengeStatus shows the consent state.

Platform age signals

Only a verified Apple iOS or Google Play signal lets the player skip the age screen. Xbox and Meta Horizon signals are accepted without error, but the player still enters an age on the phone.

What the widget handles

The widget automatically handles:

  • Age Collection: Jurisdiction-appropriate age collection methods
  • Data Notices: Data notices to accept, depending on the product's configuration in the Compliance Studio
  • Permissions: Permissions to manage, depending on the product's configuration in the Compliance Studio
  • Parental Consent Challenge: If the user is determined to be a minor, a challenge is created for trusted adult approval
  • Automatic age assurance: If the product has Automatic age assurance enabled for the jurisdiction, players who claim an age old enough to skip parental consent are asked to prove the claim (facial age estimation or ID document) inside the widget before a session is issued.

The specific flow depends on the jurisdiction and your product's configuration in the Compliance Studio.

Session creation timing with Automatic age assurance

When Automatic age assurance triggers inside the end-to-end widget, the session is created after the player passes verification rather than immediately after the age gate. The Widget.AgeGate.Result event is still fired once the flow completes and includes the sessionId on PASS. Until the event arrives, treat the flow as in progress.

For more information on implementing VPC, see the Quick Start Guide.

On this page