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.
The end-to-end and age gate widgets are fully supported on 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.
For the age gate and consent steps, building the UX natively with the custom workflow and the CDK UX guidelines typically delivers the most seamless, brand-integrated player experience. The widget UI works on mobile today and its mobile UX is being continuously optimized.
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 collectionskipVerification: Skip verification stepskipPermissions: Skip permission managementskipPreferences: 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
{
"id": "7854909b-9124-4bed-9282-24b44c4a3c97",
"url": "https://family.k-id.com/widget?token=eyJhbGciOiJFUzM4NCIs..."
}
Presenting the widget URL
The widget URL is a hosted web page. Open it in the surface that fits your application:
- Web app: embed in an iframe (example below), open as a pop-up, or redirect to it as a full page.
- Mobile app: open the widget URL in a system browser surface (Custom Tabs on Android, ASWebAuthenticationSession on iOS) and pass
options.redirectUrlto receive the result as a deep link back into your app. See the Mobile apps quick start for the display methods. For the most brand-integrated experience, consider building the age gate and consent UX natively with the custom workflow instead. - Console (Switch, PlayStation, Xbox): console browsers are typically restricted or absent. Display the widget URL as a QR code so the player completes the flow on a paired mobile device. Receive results via webhook plus
/session/getpolling, since the mobile-device redirect can't return to the console.
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
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.sessionIdis 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.agecarries 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.
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.
| Parameter | Description |
|---|---|
status | PASS, FAIL, or PROHIBITED (see below) |
sessionId | The newly created session. Present only when status is PASS. |
challengeId | The challenge involved in the flow, when one was created. |
age | The 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 withsessionId.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.
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.
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.