WebView Workflow
WebView Workflow opens the ready-made biometric verification interface inside a mobile application. The backend creates a session for a configured Workflow, and the application receives the session_id, builds a URL with the required web_view=true parameter, and loads it in an embedded WebView.
In this mode, Biometric.Vision controls the Workflow screens and technologies, while the mobile application manages camera permissions, WebView navigation, and verification completion. The API KEY remains on the backend and is never passed to the mobile application.
Integration architecture¶
Four components take part in the integration:
- the client system backend creates a session, returns its
session_idto the application, and retrieves the final result; - the mobile application manages the WebView lifecycle;
- Biometric.Vision displays the interface and runs the Workflow technologies;
- the end user grants camera access and completes the verification.
sequenceDiagram
title Embedding a Workflow in a mobile application
participant APP as Mobile application
participant API as Client system backend
participant BIO as Biometric.Vision
participant USER as End user
APP->>+API: Request to start verification
API->>API: Create Workflow session
API-->>-APP: {session_id}
APP->>+BIO: Open /flow/{session_id}?web_view=true
BIO-->>USER: Request camera access
USER-->>BIO: Grant access
BIO->>USER: Verification interface
USER->>BIO: Complete Workflow technologies
BIO-->>-APP: Navigate to /finished
APP->>APP: Intercept navigation and close WebView
APP->>API: Request current verification state
1. Build the URL¶
After receiving the session_id, open this URL in the WebView:
The web_view=true parameter is required. It enables embedded mode, in which post-verification browser redirects are replaced by navigation to this service URL:
Build the URL with the platform's standard URL API and pass only the session_id issued for the current user attempt. Do not open the same session_id in multiple WebViews at the same time.
2. Required settings¶
| Setting | Requirement | Purpose |
|---|---|---|
| JavaScript | Enabled | The verification interface runs as a web application |
| Camera access | Granted at the OS and WebView levels | Biometric verification technologies use the camera |
| Inline media playback | Enabled | Video plays inside the WebView |
| Media playback without user gesture | Enabled | The camera stream starts without an additional tap |
| Navigation delegate | Configured | The application intercepts navigation to /finished |
On iOS, add a camera usage description to Info.plist. On Android, declare android.permission.CAMERA in the manifest and request runtime permission before loading the page. Grant the WebView access only to the video-capture resource required for verification.
3. Integration examples¶
The examples accept an existing sessionId. Retrieving the identifier from the backend remains part of the application logic.
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class BiometricWebView extends StatefulWidget {
const BiometricWebView({required this.sessionId, super.key});
final String sessionId;
@override
State<BiometricWebView> createState() => _BiometricWebViewState();
}
class _BiometricWebViewState extends State<BiometricWebView> {
late final WebViewController controller;
@override
void initState() {
super.initState();
final uri = Uri.https(
'remote.biometric.vision',
'/flow/${widget.sessionId}',
{'web_view': 'true', 'locale': 'en'},
);
controller = WebViewController(
onPermissionRequest: (request) {
if (request.types.every(
(type) => type == WebViewPermissionResourceType.camera,
)) {
return request.grant();
}
return request.deny();
},
)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onNavigationRequest: (request) {
final uri = Uri.parse(request.url);
if (uri.host == 'remote.biometric.vision' &&
uri.path == '/finished') {
Navigator.of(context).pop(true);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(uri);
}
@override
Widget build(BuildContext context) {
return Scaffold(body: WebViewWidget(controller: controller));
}
}
import UIKit
import WebKit
final class BiometricViewController: UIViewController, WKNavigationDelegate {
let sessionId: String
private var webView: WKWebView!
init(sessionId: String) {
self.sessionId = sessionId
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
configuration.mediaTypesRequiringUserActionForPlayback = []
webView = WKWebView(frame: .zero, configuration: configuration)
webView.navigationDelegate = self
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
var components = URLComponents()
components.scheme = "https"
components.host = "remote.biometric.vision"
components.path = "/flow/\(sessionId)"
components.queryItems = [
URLQueryItem(name: "web_view", value: "true"),
URLQueryItem(name: "locale", value: "en")
]
webView.load(URLRequest(url: components.url!))
}
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
if let url = navigationAction.request.url,
url.host == "remote.biometric.vision",
url.path == "/finished" {
decisionHandler(.cancel)
dismiss(animated: true)
return
}
decisionHandler(.allow)
}
}
import android.os.Bundle
import android.webkit.PermissionRequest
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
class BiometricActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_biometric)
val sessionId = intent.getStringExtra("session_id") ?: run {
finish()
return
}
webView = findViewById(R.id.webView)
webView.settings.apply {
javaScriptEnabled = true
mediaPlaybackRequiresUserGesture = false
}
webView.webChromeClient = object : WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest) {
val videoCapture = request.resources.filter {
it == PermissionRequest.RESOURCE_VIDEO_CAPTURE
}.toTypedArray()
if (videoCapture.isNotEmpty()) {
request.grant(videoCapture)
} else {
request.deny()
}
}
}
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest,
): Boolean {
val url = request.url
if (request.isForMainFrame &&
url.host == "remote.biometric.vision" &&
url.path == "/finished"
) {
finish()
return true
}
return false
}
}
val url = "https://remote.biometric.vision/flow/$sessionId".toUri()
.buildUpon()
.appendQueryParameter("web_view", "true")
.appendQueryParameter("locale", "en")
.build()
webView.loadUrl(url.toString())
}
}
Check permissions before opening the WebView
The examples handle requests from the WebView, but do not replace the system camera permission request. If the user denied access at the OS level, stop the scenario and provide a way to open the application settings.
4. Handle completion¶
After the technologies finish, the WebView navigates to https://remote.biometric.vision/finished. Intercept only main-frame navigation with the exact host and path, cancel it, and close the WebView.
Navigation to /finished indicates that the interface scenario has ended, but it is not a trusted verification result. After closing the WebView, request the current result from the client system backend and update the application screen.
The completion handler must be idempotent: a repeated navigation callback must not close the screen twice or repeat a business operation.
5. URL parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
web_view |
boolean |
Yes | The value true enables WebView mode |
locale |
string |
No | Interface language: kz, en, ru, my, de, es, fa, fr, it, ja, kg, ko, pt |
isMobile |
boolean |
No | Forces mobile device mode |
documentType |
string |
No | Document type; available values depend on the Workflow configuration |
from_session_id |
string |
No | Previous session UUID for a linked-session scenario |
Example URL:
6. Common issues¶
| Problem | Cause | Solution |
|---|---|---|
| Camera does not start on iOS | Inline playback is disabled | Set allowsInlineMediaPlayback = true |
| Camera does not start on Android | System permission was not granted or the WebView request was denied | Request CAMERA from the user and handle onPermissionRequest |
| The screen is blank | JavaScript is disabled | Enable JavaScript in the WebView settings |
| The screen remains open after verification | Navigation to /finished was not intercepted |
Check the navigation delegate and host/path comparison |