> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lumiid.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Embed LumiID Hosted Widget for Identity Verification

> Embed LumiID identity verification via iframe or mobile WebView. Handles document capture, liveness checks, and results without a custom backend.

The LumiID hosted widget is a pre-built identity verification flow you embed inside your application using an HTML `<iframe>` or a mobile WebView. It handles every step of the process — document capture, liveness checks, and verification results — so you don't have to build those screens yourself. All you need is a **Widget ID** from the LumiID dashboard. You configure the widget's branding, supported countries, document types, and verification steps once, and the hosted URL does the rest.

> [!NOTE]
>
> Your Widget ID is safe to use in frontend code. Never expose your Secret API key on the client — keep it on your backend server only.

## Getting Your Widget ID

**1. Open My SDKs → Create Widget**

Log in to the LumiID dashboard and navigate to **My SDKs**, then click **Create Widget**.

**2. Configure your widget**

Set your brand, the countries you want to support, the document types to accept, and the verification steps to include.

**3. Save and copy the widget\_id**

Save your widget. Copy the generated `widget_id` from the dashboard — you'll need it in the next step.

**4. Build the hosted URL**

Replace `WIDGET_ID` in the URL below with your actual `widget_id`:

```javascript theme={null}
https://lumiid.com/widgets/sdk/verify/?widget_id=WIDGET_ID
```

***

## Web Integration

> [tab] iframe
>
> The simplest way to embed the widget on a web page is with a plain `<iframe>`. Include `allow="camera; microphone"` so the browser can prompt for camera access during liveness checks.
>
> ```javascript theme={null}
> <iframe
>   src="https://lumiid.com/widgets/sdk/verify/?widget_id=WIDGET_ID"
>   title="LumiID identity verification"
>   allow="camera; microphone"
>   allowfullscreen
> ></iframe>
> ```

> [tab] JavaScript SDK
>
> Use the JavaScript SDK when you need programmatic control — opening the modal on demand, reacting to success or failure in code, or passing metadata through to webhooks.
>
> Create one `LumiID` instance on page load. Call `.open()` when verification should begin.
>
> ```javascript theme={null}
> <button id="verify-btn">Verify Identity</button>
>
> <script>
> const verifier = new LumiID({
>   widget_id: 'wgt_3f8a2c1d-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
>   reference: 'user_123',  // your own user ID — echoed back in callbacks
>
>   onSuccess: (data) => {
>     // Verification passed ✓
>     console.log('Verified!', data.sessionId, 'Score:', data.score);
>     window.location.href = '/dashboard';
>   },
>
>   onError: (data) => {
>     // Verification failed — the modal stays open so the user can retry
>     console.warn('Failed:', data.code, data.message);
>   },
>
>   onClose: () => {
>     // User dismissed the modal without completing
>     console.log('Closed');
>   },
> });
>
> document.getElementById('verify-btn').onclick = () => verifier.open();
> </script>
> ```
>
> On success, the modal closes automatically and `onSuccess` fires. On failure, the modal stays open for another attempt. Call `verifier.close()` inside `onError` if you want to close the modal after a failed attempt.

### JavaScript SDK Configuration

| Option | Type | Required | Description |
| - | - | - | - |
| `widget_id` | string | required | Your widget UUID from the LumiID dashboard. |
| `reference` | string | optional | Your own user ID or reference string. Echoed back in all callbacks so you can tie a result to a user. |
| `onSuccess` | function | optional | Called when verification completes successfully. The modal closes automatically before this fires. Receives a result payload. |
| `onError` | function | optional | Called when verification fails. The modal stays open by default. Receives an error payload with a `code` and `message`. |
| `onClose` | function | optional | Called when the user dismisses the modal without completing (clicks outside, presses Escape, or taps the close button). |
| `metadata` | object | optional | Arbitrary key-value pairs forwarded unchanged to all callbacks and webhook payloads. |

### SDK Methods

| Method | What it does |
| - | - |
| `verifier.open()` | Opens the verification modal. Calling it again while the modal is already open is a safe no-op. Returns `this` so you can chain. |
| `verifier.close()` | Programmatically closes the modal and fires `onClose`. Call this from inside `onError` to close after a failed attempt. |
| `verifier.destroy()` | Removes all DOM elements and event listeners permanently. Use this when unmounting a component. The instance cannot be reused after this call. |
| `LumiID.prefetch(id)` | Static helper. Pre-warms the widget config in the background so the first `open()` feels instant. Call it after your page has loaded — pass the widget ID. |

> [!TIP]
>
> Call `LumiID.prefetch('wgt_...')` after page load to pre-fetch the widget configuration. This makes the first `open()` appear instant for users.

***

## Mobile Integration

Load the hosted URL inside a mobile WebView. Your WebView must grant camera permissions — liveness checks require live camera access.

The widget sends events through platform-specific bridges. Implement the bridge for your platform to receive verification results.

> [tab] iOS (Swift)
>
> **Requirements:** Swift 5, WKWebView
>
> Add `NSCameraUsageDescription` to your **Info.plist**. Without it, iOS denies camera access.
>
> ```swift theme={null}
> import WebKit
>
> class VerificationViewController: UIViewController, WKScriptMessageHandler {
>
>     var webView: WKWebView!
>
>     override func viewDidLoad() {
>         super.viewDidLoad()
>
>         let config = WKWebViewConfiguration()
>         config.allowsInlineMediaPlayback = true                  // required for camera
>         config.mediaTypesRequiringUserActionForPlayback = []      // no tap-to-play
>
>         // Register the bridge — the SDK sends to window.webkit.messageHandlers.lumiid
>         config.userContentController.add(self, name: "lumiid")
>
>         webView = WKWebView(frame: view.bounds, configuration: config)
>         webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
>         view.addSubview(webView)
>
>         let url = URL(string: "https://lumiid.com/widgets/sdk/verify/?widget_id=YOUR_WIDGET_ID")!
>         webView.load(URLRequest(url: url))
>     }
>
>     // Called every time the SDK fires an event
>     func userContentController(
>         _ controller: WKUserContentController,
>         didReceive message: WKScriptMessage
>     ) {
>         guard message.name == "lumiid",
>               let body = message.body as? [String: Any],
>               let event = body["event"] as? String
>         else { return }
>
>         switch event {
>
>         case "verification_complete":
>             let status = body["status"] as? String
>             if status == "success" {
>                 let sessionId = body["sessionId"] as? String ?? ""
>                 handleSuccess(sessionId: sessionId)
>             } else {
>                 handleFailure()
>             }
>
>         case "sdk_ready":
>             print("LumiID ready. Session:", body["sessionId"] ?? "")
>
>         default:
>             break
>         }
>     }
>
>     func handleSuccess(sessionId: String) {
>         // Dismiss WebView, mark user verified, navigate, etc.
>         dismiss(animated: true)
>     }
>
>     func handleFailure() {
>         // Optionally dismiss or let the user retry inside the WebView
>     }
> }
> ```

> [tab] Android (Kotlin)
>
> **Requirements:** Kotlin, WebView
>
> Add `CAMERA` to your **AndroidManifest.xml** and request this permission at runtime on Android 6 and later before loading the WebView.
>
> ```xml theme={null}
> <uses-permission android:name="android.permission.CAMERA" />
> ```
>
> ```kotlin theme={null}
> import android.webkit.*
> import org.json.JSONObject
>
> class VerificationActivity : AppCompatActivity() {
>
>     override fun onCreate(savedInstanceState: Bundle?) {
>         super.onCreate(savedInstanceState)
>         val webView = WebView(this)
>         setContentView(webView)
>
>         webView.settings.apply {
>             javaScriptEnabled = true
>             mediaPlaybackRequiresUserGesture = false   // required for camera
>             allowFileAccess = true
>         }
>
>         // Inject the JS interface — SDK calls window.LumiIDAndroid.onEvent(json)
>         webView.addJavascriptInterface(LumiIDBridge(), "LumiIDAndroid")
>
>         // Handle camera permission prompt
>         webView.webChromeClient = object : WebChromeClient() {
>             override fun onPermissionRequest(request: PermissionRequest) {
>                 request.grant(request.resources)
>             }
>         }
>
>         webView.loadUrl("https://lumiid.com/widgets/sdk/verify/?widget_id=YOUR_WIDGET_ID")
>     }
>
>     inner class LumiIDBridge {
>         @JavascriptInterface
>         fun onEvent(jsonString: String) {
>             val json = JSONObject(jsonString)
>             when (json.optString("event")) {
>                 "verification_complete" -> {
>                     val status = json.optString("status")
>                     val sessionId = json.optString("sessionId")
>                     runOnUiThread {
>                         if (status == "success") handleSuccess(sessionId)
>                         else handleFailure()
>                     }
>                 }
>                 "sdk_ready" -> {
>                     // SDK initialised, session created
>                 }
>             }
>         }
>     }
>
>     fun handleSuccess(sessionId: String) {
>         // Navigate away, mark user verified, etc.
>     }
>
>     fun handleFailure() {
>         // Optionally finish() or let the user retry inside the WebView
>     }
> }
> ```

> [tab] React Native
>
> **Requirements:** `react-native-webview`
>
> Install the package first:
>
> ```bash theme={null}
> npm install react-native-webview
> # iOS: cd ios && pod install
> ```
>
> ```jsx theme={null}
> import React from 'react';
> import { StyleSheet } from 'react-native';
> import { WebView } from 'react-native-webview';
>
> export default function VerificationScreen({ widgetId, onComplete }) {
>
>   const handleMessage = (event) => {
>     try {
>       const msg = JSON.parse(event.nativeEvent.data);
>
>       if (msg.event === 'verification_complete') {
>         const passed = msg.status === 'success';
>         onComplete(passed, msg.sessionId);
>       }
>     } catch (e) { /* ignore non-JSON messages */ }
>   };
>
>   return (
>     <WebView
>       style={styles.webview}
>       source={{
>         uri: `https://lumiid.com/widgets/sdk/verify/?widget_id=${widgetId}`,
>       }}
>       onMessage={handleMessage}
>       mediaCapturePermissionGrantType="grant"
>       allowsInlineMediaPlayback
>       mediaPlaybackRequiresUserAction={false}
>       javaScriptEnabled
>       originWhitelist={['*']}
>     />
>   );
> }
>
> const styles = StyleSheet.create({
>   webview: { flex: 1 },
> });
> ```
>
> Use it inside a navigation flow like this:
>
> ```jsx theme={null}
> import VerificationScreen from './VerificationScreen';
>
> function OnboardingFlow({ navigation }) {
>   return (
>     <VerificationScreen
>       widgetId="wgt_3f8a2c1d-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
>       onComplete={(passed, sessionId) => {
>         if (passed) navigation.replace('Home');
>         else navigation.goBack();
>       }}
>     />
>   );
> }
> ```

> [tab] Flutter
>
> **Requirements:** `webview_flutter` 4.0 or later
>
> Add the dependency to your `pubspec.yaml`:
>
> ```yaml theme={null}
> dependencies:
>   webview_flutter: ^4.4.0
> ```
>
> On iOS, add `NSCameraUsageDescription` to **Info.plist**. On Android, add `CAMERA` to **AndroidManifest.xml**.
>
> ```dart theme={null}
> import 'dart:convert';
> import 'package:flutter/material.dart';
> import 'package:webview_flutter/webview_flutter.dart';
>
> class VerificationPage extends StatefulWidget {
>   final String widgetId;
>   final Function(bool passed, String sessionId) onComplete;
>
>   const VerificationPage({
>     required this.widgetId,
>     required this.onComplete,
>     super.key,
>   });
>
>   @override
>   State<VerificationPage> createState() => _VerificationPageState();
> }
>
> class _VerificationPageState extends State<VerificationPage> {
>   late WebViewController _controller;
>
>   @override
>   void initState() {
>     super.initState();
>
>     _controller = WebViewController()
>       ..setJavaScriptMode(JavaScriptMode.unrestricted)
>       ..addJavaScriptChannel(
>         'LumiIDFlutter',          // window.LumiIDFlutter.postMessage(...)
>         onMessageReceived: (msg) {
>           try {
>             final data = jsonDecode(msg.message) as Map<String, dynamic>;
>             if (data['event'] == 'verification_complete') {
>               final passed = data['status'] == 'success';
>               final sessionId = data['sessionId'] ?? '';
>               widget.onComplete(passed, sessionId);
>             }
>           } catch (_) {}
>         },
>       )
>       ..loadRequest(Uri.parse(
>         'https://lumiid.com/widgets/sdk/verify/?widget_id=${widget.widgetId}',
>       ));
>   }
>
>   @override
>   Widget build(BuildContext context) => WebViewWidget(controller: _controller);
> }
> ```

***

## Widget Events

The widget sends events through supported platform bridges. Every event includes an `event` field and a `ts` field (Unix timestamp in milliseconds).

| Event | When it fires | Key fields |
| - | - | - |
| `sdk_ready` | Widget configuration and session are ready. | `sessionId` |
| `screen_change` | The user moves through the verification flow. | `screenId` |
| `country_selected` | The user selects a country. | `country`, `countryName` |
| `idtype_selected` | The user selects a document type. | `idType` |
| `verification_result` | A verification result is available. | `status`, `score`, `sessionId`, `country`, `idType` |
| `verification_complete` | The user completes the result-screen action. | `status`, `sessionId` |
| `retry` | The user restarts the complete flow. | — |
| `retry_liveness` | The user retries only the face scan. | — |

> [!TIP]
>
> Use `verification_complete` as your primary signal to close a WebView or redirect a user — not `verification_result`. The complete event fires after the user has acknowledged the result screen.

***

## Quick Reference

| Item | Value |
| - | - |
| Hosted widget URL | `https://lumiid.com/widgets/sdk/verify/?widget_id=WIDGET_ID` |
| iOS bridge | `window.webkit.messageHandlers.lumiid` |
| Android bridge | `window.LumiIDAndroid.onEvent(json)` |
| Completion event | `verification_complete` |
| Success status | `success` |
| Failure status | `failed` |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.