Skip to content

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_id to 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:

https://remote.biometric.vision/flow/<session_id>?web_view=true

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:

https://remote.biometric.vision/finished

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:

https://remote.biometric.vision/flow/<session_id>?web_view=true&locale=en&isMobile=true

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