Live ID ValidationMobile onboarding

Mobile onboarding

Embed the Live ID hosted capture page in a native app — WebView setup and camera permissions for iOS, Android, and .NET MAUI, plus ready-to-run sample apps.

In a native app, you embed the hosted capture page in a WebView rather than building your own camera UI. The WebView runs the same flow as the web — selfie, face match, liveness — and your app reads the verdict by polling the session.

The hosted page sets frame-ancestors 'none', so it cannot be put in a web iframe. A native WebView is a top-level load, not an iframe, so this is fine — and the right pattern (locked v1 design).

The pattern

graph TD
  A[App: user taps Log in] --> B[Ask your backend to mint a session]
  B --> C[Backend returns captureUrl + sessionToken]
  C --> D[Open captureUrl in a WebView]
  D --> E[WebView: selfie -> face match + liveness]
  A --> F[Poll GET …/sessions/sessionToken every 2s]
  E --> F
  F --> G{status == completed?}
  G -->|yes| H[Read matched -> grant or deny, close WebView]
  G -->|no| F

Two things every platform needs:

  1. Mint server-side, hold only the token. The app calls your backend, which calls Certivant's POST /sessions with the Identification Key. The app receives only captureUrl + sessionToken.
  2. Grant the WebView's camera request. getUserMedia inside a WebView is blocked until you explicitly grant it — the hook differs per platform (below).

Camera permission per platform

iOS (WKWebView). Declare NSCameraUsageDescription, allow inline media, and grant the capture request via WKUIDelegate (iOS 15+):

let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []
// WKUIDelegate:
func webView(_ webView: WKWebView,
             requestMediaCapturePermissionFor origin: WKSecurityOrigin,
             initiatedByFrame frame: WKFrameInfo,
             type: WKMediaCaptureType,
             decisionHandler: @escaping (WKPermissionDecision) -> Void) {
    decisionHandler(.grant)
}

Android (WebView). Add the CAMERA manifest permission, request it at runtime, and grant the page's request in WebChromeClient:

webView.settings.javaScriptEnabled = true
webView.settings.mediaPlaybackRequiresUserGesture = false
webView.webChromeClient = object : WebChromeClient() {
    override fun onPermissionRequest(req: PermissionRequest) {
        runOnUiThread { req.grant(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) }
    }
}

.NET MAUI. Customize the shared WebView handler once; each platform wires its own grant (Android WebChromeClient, iOS/Mac WKUIDelegate, Windows WebView2 PermissionRequested). Add NSCameraUsageDescription (iOS/Mac), the CAMERA permission (Android), and the webcam capability (Windows).

Microsoft.Maui.Handlers.WebViewHandler.Mapper.AppendToMapping(
    "LiveIdCamera", (handler, view) => {
#if ANDROID
        handler.PlatformView.SetWebChromeClient(new LiveIdChromeClient());
#elif IOS || MACCATALYST
        handler.PlatformView.UIDelegate = new LiveIdUIDelegate();
#elif WINDOWS
        // CoreWebView2.PermissionRequested -> Allow (Camera)
#endif
    });

Reading the result

Poll the status endpoint and stop when it completes:

GET https://app.certivant.com/api/kyc/public/live-id/sessions/{sessionToken}
→ { "status": "completed", "matched": true, … }

matched === true → grant login. Your backend can also subscribe to the live_id.validation.completed webhook for an authoritative server-side signal — useful when you want the grant decision to live entirely on your backend.

Sample apps

Complete, runnable references live in the repository under samples/live-id-validation/:

SampleStackTargets
android-kotlin/KotlinAndroid
ios-swiftui/SwiftUIiOS 15+
maui/.NET MAUIAndroid · iOS · Windows · macOS

Each is one screen: tap Verify with selfie → WebView → result. Set your host and a session source in the sample's LiveIdConfig (your backend endpoint, or a curl-minted token for a quick test), then run. See each sample's README for build steps and the exact files that wire up the camera grant and polling.