Skip to main content

Mobile apps

This guide covers best practices for integrating k-ID age verification into mobile applications. On the web, k-ID interfaces are commonly embedded in iframes, but mobile apps need different approaches to display verification URLs effectively.

Overview

The display methods in this guide apply to any hosted k-ID URL, including the widget URLs. The examples focus on the hosted URLs that present a jurisdiction-aware verification interface (AgeKeys, facial age estimation, ID verification, and other methods). Two kinds are covered:

The examples in this guide use the /age-verification/perform-access-age-verification endpoint, but the same display methods and result handling apply to any of these URLs.

When embedding one of these URLs in a mobile application, you have several options for displaying it, each with different capabilities and trade-offs. The key considerations are:

  • AgeKeys support: Whether users can create and use AgeKeys (FIDO-based passkeys) for future verifications
  • Result communication: How verification results are delivered back to your app
  • User experience: The level of integration and native feel
  • Device orientation: Verification works best in portrait. In-app browser surfaces inherit your app's orientation, so if your app is locked to landscape, use the default external browser instead to let users rotate to portrait. See Device orientation
Age gate and end-to-end widgets on mobile

The age gate and end-to-end widgets are fully supported on mobile and are displayed with the same methods described in this guide. For the age gate and consent steps, though, building the UX natively with the custom workflow and the CDK UX guidelines typically delivers the most seamless, brand-integrated player experience, and the widget's mobile UX is being continuously optimized.

Mobile implementation methods

Android options

Android provides three primary methods for displaying verification URLs:

  • Custom TabsRecommended - Opens the verification URL in a customized Chrome browser tab that maintains your app's branding. This provides full browser capabilities while keeping users within your app's context. Custom Tabs share cookies and authentication state with Chrome, enabling seamless experiences.

  • WebView - Embeds web content directly within your app using Android's native WebView component. While simple to implement, WebView has limited support for modern web standards and can't access certain browser features.

Not recommended

WebView doesn't support WebAuthn, which is required for AgeKeys. Users won't be able to create or use AgeKeys when a verification URL is embedded using WebView. For the best user experience with full AgeKeys support, use Custom Tabs instead.

  • Trusted Web Activity (TWA) - Displays web content in full-screen mode, primarily designed for Progressive Web Apps. TWAs require establishing a digital asset link between your app and the k-ID domain. When digital asset links aren't detected, TWA automatically falls back to Custom Tabs.
Not recommended

Trusted Web Activities aren't supported for embedding verification URLs. Digital asset links would need to be configured on k-ID's domains, which isn't available. Since TWA falls back to Custom Tabs in this case, use Custom Tabs directly for a simpler implementation.

iOS options

iOS provides three primary methods for displaying verification URLs:

  • ASWebAuthenticationSessionRecommended - Designed specifically for secure authentication flows, this method presents web content in a system-managed browser view. It shares cookies with Safari and provides access to modern web features such as WebAuthn, making it ideal for verification flows.

  • SFSafariViewController - Presents web content in a Safari-like interface that shares cookies and authentication state with Safari. This provides a familiar browsing experience while maintaining app context.

  • WKWebView - Apple's modern web view component that embeds web content within your app. Similar to Android's WebView, WKWebView has limitations with certain web standards and can't access all browser features.

Not recommended

WKWebView doesn't support WebAuthn, which is required for AgeKeys. Users won't be able to create or use AgeKeys when a verification URL is embedded using WKWebView. For the best user experience with full AgeKeys support, use ASWebAuthenticationSession instead.

Default browser (Android and iOS)

The default external browser works on both Android and iOS. Instead of presenting the verification URL in an in-app browser surface, you launch the user's default browser as a separate app (Intent.ACTION_VIEW on Android, UIApplication.open on iOS).

  • Full AgeKeys support - The browser provides WebAuthn, so users can create and use AgeKeys.
  • Independent orientation - Because the browser is a separate app, it manages its own orientation. This makes it the recommended method for apps with a locked landscape orientation, since the user can rotate to portrait for the best verification experience. See Device orientation.
  • Callback URL required - The user leaves your app, so results are delivered through the redirectUrl callback, which also returns focus to your app when the flow completes. DOM messages aren't available.

AgeKeys support limitations

AgeKeys are reusable, anonymous age-proof credentials based on FIDO and WebAuthn standards. They allow users to verify their age once and reuse that verification across different services without revealing personal information.

AgeKeys limitation

AgeKeys require WebAuthn support, which isn't available in Android WebView or iOS WKWebView. If you embed a verification URL by using these components, users won't see AgeKeys as an option during verification, and they can't create AgeKeys after successful verification.

To enable AgeKeys for your users, you must use one of these methods:

Device orientation

Age verification works best in portrait orientation. Methods such as facial age estimation and ID document capture are easier to complete when the device is upright, and the verification interface is laid out for portrait.

In-app browser surfaces (Custom Tabs, ASWebAuthenticationSession, SFSafariViewController, and WebView/WKWebView) inherit your app's orientation constraints. If your game or app locks to landscape, the verification interface is forced into landscape too, which degrades the experience.

Recommendation for landscape-locked apps

If your app has a locked landscape orientation, open the verification URL in the device's default external browser instead of an in-app browser surface. The external browser runs as a separate app and manages its own orientation, so the user can rotate their device to portrait for the best experience.

Set a redirectUrl callback when generating the verification URL. When the user completes the verification flow, the browser redirects to your deep link, which returns focus to your app or game.

The external browser fully supports AgeKeys (WebAuthn is available). As with Custom Tabs and ASWebAuthenticationSession, DOM messages aren't available, so you must use a callback URL to receive results.

Open the verification URL in the default browser as follows:

import UIKit

func displayVerificationInBrowser(verificationUrl: URL) {
// Opens the system default browser (a separate app), which manages
// its own orientation regardless of your app's locked orientation.
UIApplication.shared.open(verificationUrl)
}

Handle the returning deep link exactly as shown in Step 4.

Receiving verification results

Your mobile app needs to receive results after users complete the verification flow. There are two approaches, each with different availability:

For detailed information about analyzing verification results, including field presence rules, status types, and implementation guidance, see the Verification Event Contract.

Callback URL (universal method)

Recommended approach

The recommended approach for all implementation methods is to use a callback URL. When you call the API to generate a verification URL, include a redirectUrl parameter. After the verification flow completes, it redirects to this URL with the results included as query parameters.

Advantages of callback URLs:

  • Works with all implementation methods
  • More reliable than DOM messages
  • Standard deep linking pattern for mobile apps
  • Results are always delivered, even if the app moves to the background

How callback URLs work

  1. Register a deep link handler in your app (for example, myapp://verification-complete)
  2. Include the deep link as redirectUrl when calling the API
  3. The verification page redirects to your deep link after completion
  4. Your app handles the deep link and extracts the results
note

Redirects only occur when the verification URL is opened directly in a browser or web view, not when embedded in an iframe.

Callback URL parameters

When the verification page redirects to your callback URL, it includes query parameters relevant to the flow. For example:

  • Age verification includes verificationId and result
  • When the URL came from a session upgrade, it can also include sessionId and status information

Example callback URL:

myapp://verification-complete?verificationId=7854909b-9124-4bed-9282-24b44c4a3c97&result=PASS

Implementing callback URLs

Include redirectUrl in your request when calling the age verification API. The URL can be:

  • An HTTPS URL: https://example.com/verification-complete
  • A custom deep link: myapp://verification-complete

DOM messages (WebView/WKWebView only)

When using Android WebView or iOS WKWebView, you can listen for JavaScript messages sent from the verification page. This allows you to:

  • Receive verification results in real-time
  • Control when the web view closes
  • Update your app's UI based on verification events

The age verification interface emits the Verification.Result event, which includes the status (such as PASS or FAIL) and, on success, the resolved ageCategory.

DOM messages are sent as postMessage events that you can intercept in your native code. For details about available events, see the DOM events overview.

Limited availability

DOM messages only work with WebView and WKWebView, which don't support AgeKeys. They're not available with Custom Tabs, Trusted Web Activity, ASWebAuthenticationSession, or SFSafariViewController. Because these components can't create AgeKeys, use a callback URL with a recommended display method instead.

Platform-specific implementation

For iOS WKWebView, you can receive k-ID events natively by registering a message handler named kid. The verification page automatically detects this handler and sends events directly to it. However, Android WebView doesn't have a native mechanism to receive postMessage events. You must inject JavaScript to listen for messages and forward them to your native code via a JavaScript interface.

Third-party app verification flows

Some verification methods, such as ConnectID, require redirecting users to a third-party mobile app as part of the verification process. For example, ConnectID opens the user's banking app to complete identity verification.

How third-party app flows work

When using verification methods that involve third-party apps, the flow passes through multiple applications before returning to your app:

Step-by-step breakdown:

  1. Your app requests a verification URL from your server
  2. Your server calls the k-ID API with your API key, including the app's redirectUrl
  3. k-ID returns a URL containing the verification interface to your server
  4. Your server returns the URL to your app
  5. Your app opens the URL in a web component (ASWebAuthenticationSession or Custom Tabs)
  6. When the user selects a verification method such as ConnectID, the k-ID UI deep links to the third-party verification app (such as a banking app)
  7. After verification completes, the third-party app redirects back to a k-ID result page in the device's native browser
  8. k-ID retrieves your stored redirectUrl and redirects the user back to your app with the verification result

Key considerations for third-party app flows

Web component requirement - These verification methods must be opened in a system browser context (ASWebAuthenticationSession, Custom Tabs, or SFSafariViewController) rather than an embedded WebView. The third-party app redirect flow requires the full browser context to work correctly.

Native browser handoff - After the third-party app completes verification, it redirects to a k-ID URL that opens in the device's native browser rather than returning directly to your original web component. This is a platform limitation with how app-to-app redirects work on mobile devices.

Callback URL is essential - Since the verification flow passes through multiple apps and browsers, the redirectUrl parameter is critical for returning users to your app after completion. Always include a redirectUrl when initiating verifications that might use third-party app methods.

Testing third-party app flows

The following verification methods use third-party app redirects:

  • ConnectID: Includes test apps for validating the redirect flow in mobile applications.

Method comparison

MethodPlatformAgeKeysDOM MessagesCallback URLBest For
WebViewAndroidNot recommended (no AgeKeys support)
Custom TabsAndroidMost use cases (recommended)
Trusted Web ActivityAndroidNot recommended (requires digital asset links)
WKWebViewiOSNot recommended (no AgeKeys support)
ASWebAuthenticationSessioniOSMost use cases (recommended)
SFSafariViewControlleriOSSafari-like experience
Default external browserAndroid & iOSLandscape-locked apps (allows portrait rotation)

Android: Custom Tabs

Use Custom Tabs with callback URLs for the best balance of features and user experience.

Why Custom Tabs:

  • Full AgeKeys support via WebAuthn
  • Access to all modern web features
  • Seamless user experience with app branding
  • Reliable callback mechanism
  • Shares authentication state with Chrome

Implementation steps:

  1. Register a deep link handler for your callback URL
  2. Include redirectUrl in your API request
  3. Open the verification URL using Custom Tabs
  4. Handle the deep link callback with verification results

iOS: ASWebAuthenticationSession

Use ASWebAuthenticationSession with callback URLs for secure, native-feeling compliance flows.

Why ASWebAuthenticationSession:

  • Full AgeKeys support via WebAuthn
  • Access to all modern web features
  • System-managed security UI
  • Shares cookies with Safari
  • Reliable callback mechanism

Implementation steps:

  1. Register a URL scheme handler for your callback URL
  2. Include redirectUrl in your API request
  3. Present the verification URL using ASWebAuthenticationSession
  4. Handle the URL scheme callback with verification results

Complete implementation example

Here's a step-by-step example of implementing the recommended approach with complete code samples:

Register a URL scheme in Info.plist:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>

Step 2: Generate the verification URL from your server

Important

The verification URL must be generated from your server, not directly from the mobile app. This protects your API key from being exposed in client-side code. Your mobile app should call your own server API, which then makes the server-to-server call to k-ID.

Architecture overview

Server implementation

Your server calls the /age-verification/perform-access-age-verification endpoint with your API key, including the redirectUrl deep link:

POST https://game-api.k-id.com/api/v1/age-verification/perform-access-age-verification
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY

{
"jurisdiction": "US-CA",
"criteria": {
"ageCategory": "DIGITAL_YOUTH_OR_ADULT"
},
"options": {
"redirectUrl": "myapp://verification-complete"
}
}
note

For testing, use the test environment endpoint: https://game-api.test.k-id.com/api/v1/age-verification/perform-access-age-verification

Response:

{
"id": "7854909b-9124-4bed-9282-24b44c4a3c97",
"url": "https://family.k-id.com/verify?token=eyJhbGciOiJFUzM4NCIs...",
"shortUrl": "https://family.k-id.com/v/7854909b-9124-4bed-9282-24b44c4a3c97?pid=42&s=qr"
}
Other supported mobile URLs

The same display and result handling apply to age assurance challenge URLs: the challenge.url of a CHALLENGE_AGE_GATE_AGE_ASSURANCE challenge from /age-gate/check (when automatic age assurance is enabled) and a CHALLENGE_SESSION_UPGRADE_BY_AGE_ASSURANCE challenge from /session/upgrade. Only the endpoint that produces the URL differs.

Mobile client implementation

Your mobile app calls your server to get the verification URL:

import Foundation

func fetchVerificationUrl(completion: @escaping (URL?) -> Void) {
// Call YOUR server endpoint, not k-ID directly
guard let url = URL(string: "https://your-server.com/api/generate-verification-url") else {
completion(nil)
return
}

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// Add your own authentication (session token, etc.)
request.setValue("Bearer USER_SESSION_TOKEN", forHTTPHeaderField: "Authorization")

let requestBody: [String: Any] = [
"jurisdiction": "US-CA",
"criteria": [
"ageCategory": "DIGITAL_YOUTH_OR_ADULT"
],
"options": [
"redirectUrl": "myapp://verification-complete"
]
]

guard let httpBody = try? JSONSerialization.data(withJSONObject: requestBody) else {
completion(nil)
return
}
request.httpBody = httpBody

URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil,
let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode),
let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let verificationUrlString = json["url"] as? String,
let verificationUrl = URL(string: verificationUrlString) else {
completion(nil)
return
}
completion(verificationUrl)
}.resume()
}

Step 3: Display the verification URL

import AuthenticationServices

// Store session as a property to prevent deallocation
var authSession: ASWebAuthenticationSession?

func displayVerification(verificationUrl: URL) {
authSession = ASWebAuthenticationSession(
url: verificationUrl,
callbackURLScheme: "myapp"
) { callbackURL, error in
if let error = error {
// Handle error (user cancelled, etc.)
return
}
if let callbackURL = callbackURL {
handleVerificationCallback(callbackURL)
}
}
authSession?.presentationContextProvider = self
authSession?.start()
}

Step 4: Handle the callback

func handleVerificationCallback(_ callbackURL: URL) {
// Check if this is a callback URL we're expecting
guard callbackURL.scheme == "myapp",
callbackURL.host == "verification-complete",
let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems else {
return
}

let verificationId = queryItems.first(where: { $0.name == "verificationId" })?.value
let result = queryItems.first(where: { $0.name == "result" })?.value
// Present only when the verification URL came from a session upgrade
let sessionId = queryItems.first(where: { $0.name == "sessionId" })?.value

// Update UI based on the verification result
if result == "PASS" {
// Handle successful verification
} else if result == "FAIL" {
// Handle failed verification
}

// Optionally verify server-side using API endpoints
if let verificationId = verificationId {
verifyResultServerSide(verificationId: verificationId)
} else if let sessionId = sessionId {
verifyResultServerSide(sessionId: sessionId)
}
}
Best practice

Always verify results server-side using the appropriate API endpoint rather than relying solely on client-side data for security and data integrity. Use the /age-verification/get-status endpoint for a verification, or /session/get when the verification URL came from a session upgrade. For detailed information about analyzing verification results, including field presence rules, status types, and implementation guidance, see the Verification Event Contract. If you rely on webhooks, see Delivery, retries, and recovery for the retry policy and how to recover from missed events.

Hosted URLs on mobile

The display methods and result handling in this guide apply to all hosted k-ID URLs, including the age gate and end-to-end widget URLs and these verification URLs:

For the age gate and consent flows, we recommend building the UX natively with the custom workflow, following the CDK UX guidelines, for the most seamless, brand-integrated experience, though the age gate and end-to-end widgets are fully supported on mobile as well.