Using the SDK in a WebView or iframe
The Fortface Web SDK works embedded in an iframe, in an Android WebView and in an iOS WKWebView — but in those scenarios the host app controls the environment, not the browser. Three things start depending on your configuration:
- Permissions — camera and, on Android, the passive motion signals;
- Rendering — hardware acceleration, used in the device security validations;
- Device identity — the
User-Agent, which reports which device is capturing.
In all scenarios: HTTPS is mandatory (secure context — getUserMedia and devicemotion do not exist over HTTP). See the requirement in CDN Integration and the FORTFACE_INSECURE_PROTOCOL error.
Iframe (web embed)
Add the allow attribute on the iframe that loads the flow:
<iframe
src="https://YOUR-FLOW..."
allow="camera; microphone; accelerometer; gyroscope; magnetometer"
></iframe>
camerais mandatory. Without it, a cross-origin iframe hasgetUserMediablocked by Permissions-Policy. In that scenario, the SDK may return thecameraAccessPolicyRestrictederror.accelerometer; gyroscope; magnetometerenable passive motion collection on Chromium (Android). Without them, capture still works, but motion signals are not collected.- Include
microphone. The SDK does not capture audio, but the audio device count is one of the signals in the device security validations — without this permission, enumeration is incomplete. - Do not use
sandboxon the iframe (the<iframe>HTML attribute that restricts what the embedded content is allowed to do, camera access included). If it is unavoidable, you need at leastsandbox="allow-scripts allow-same-origin"— and fully test the flow before shipping. - The host page must not deny these permissions in the
Permissions-Policyheader of its own HTTP response.
Android WebView
Two layers: the app needs the camera permission, and the WebView must forward it to the page.
AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
Runtime: request Manifest.permission.CAMERA from the user before opening the flow (the WebView does not show its own prompt — if the app does not have the permission, the grant below fails silently).
WebView configuration:
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true // localStorage
mediaPlaybackRequiresUserGesture = false
}
webView.webChromeClient = object : WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest) {
// Recommended: validate request.origin against the flow domain.
request.grant(request.resources) // includes RESOURCE_VIDEO_CAPTURE
}
}
- Without
onPermissionRequest,getUserMediainside the WebView is denied even if the app has the permission. - Make sure
mediaPlaybackRequiresUserGestureis set tofalse. By default, most WebView implementations set this totrue, which prevents automatic video playback without user interaction. This restriction affects the Fortface Web SDK experience, because it prevents the camera feed from being displayed automatically to help the user position themselves correctly for capture. - Do not disable hardware acceleration on the Activity/WebView: this switches the WebGL renderer to software and distorts GPU signals used in device security validations.
devicemotion(sensors) works in the WebView without additional Android permissions.
It is worth noting that, even if your application was not specifically developed to run inside a WebView, it may still be executed within third-party webviews, such as in-app browsers embedded in apps like Instagram or Telegram. In these scenarios, which are usually more limited and keep the default configuration enabled (true), the Fortface Web SDK will display a button so that the user can manually start the camera, ensuring the necessary consent to start the video stream.

Note: There are rare cases where third-party webviews do not persist the permission granted by the user to access the camera through the Fortface Web SDK. In such situations, the permission dialog may appear more than once, requiring the user to grant camera access again.
iOS WKWebView
Platform requirement: iOS 14.3+ (before that, getUserMedia does not exist in WKWebView). The Fortface Web SDK requires at least iOS 15.4 — see Compatibility and Requirements.
Info.plist:
<key>NSCameraUsageDescription</key>
<string>We use the camera for identity verification.</string>
Configuration:
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true // inline preview, not fullscreen
config.mediaTypesRequiringUserActionForPlayback = []
let webView = WKWebView(frame: .zero, configuration: config)
Camera permission (iOS 15+, recommended): without this delegate, WebKit shows its own prompt on every session, on top of the system prompt. Implement WKUIDelegate and auto-approve for the flow domain:
@available(iOS 15.0, *)
func webView(_ webView: WKWebView,
requestMediaCapturePermissionFor origin: WKSecurityOrigin,
initiatedByFrame frame: WKFrameInfo,
type: WKMediaCaptureType,
decisionHandler: @escaping (WKPermissionDecision) -> Void) {
// Validate origin.host against the flow domain before granting.
decisionHandler(origin.host == "YOUR-DOMAIN" ? .grant : .prompt)
}
- Requesting the system camera permission (
AVCaptureDevice.requestAccess(for: .video)) before opening the flow improves UX (a single prompt, at the right time). - Motion sensors are not collected on iOS by design (the SDK never calls
DeviceMotionEvent.requestPermission()to avoid triggering the modal) — no configuration needed.
User-Agent: append, never replace
If your app swaps the whole User-Agent string for a fixed one, all of your users start reporting themselves as the same device. The real model stops reaching us, which degrades the anti-fraud analysis and makes it harder for support to investigate device-specific problems.
The default WebView UA already carries the real device model:
Mozilla/5.0 (Linux; Android 15; moto g54 5G Build/…; wv) …
When the app replaces that string, the model becomes a fixed lie, identical for all of your users. When the app appends its own token to the end of the default UA, the model stays correct and the app remains identifiable — that is always the way to go.
How to do it
// Android WebView — CORRECT: append to the default
val settings = webView.settings
settings.userAgentString = "${settings.userAgentString} MyApp/1.2.3"
// Android WebView — WRONG: replaces the whole string
settings.userAgentString =
"Mozilla/5.0 (Linux; Android 10; Pixel 4) Chrome/101.0.4951.64 MyApp/1.2.3"
// React Native WebView — use applicationNameForUserAgent (appends).
// The `userAgent` prop replaces: do not use it.
<WebView applicationNameForUserAgent="MyApp/1.2.3" />
// WKWebView — use applicationNameForUserAgent (appends).
// `webView.customUserAgent` replaces: do not use it.
webView.configuration.applicationNameForUserAgent = "MyApp/1.2.3"
In Flutter (webview_flutter) there is no append API: setUserAgent always replaces. If you need to tag the app, read navigator.userAgent from the WebView first and concatenate your token to that value — never write a hand-crafted string.
If the goal is just to let your backend know the call came from the app, prefer a custom HTTP header or a field in the SDK initialization. The User-Agent exists for the browser to describe the device; app identity is not its job. That way the problem stops existing instead of depending on getting the concatenation right.
How to check your integration
Run this in the WebView context (via evaluateJavascript, remote DevTools or a test page):
console.log(navigator.userAgent);
Check that your token is at the end of the string and that the rest of it belongs to the WebView of the test device. The warning sign is a UA carrying a model that is not the device in your hand — that means your WebView is replacing the string.
Some browsers do not expose the device model in the UA — that is normal and requires nothing from you. Do not try to "fix" it by writing a UA by hand: that is exactly the problem described above.
Checklist
- HTTPS on the host and on the flow.
-
allow="camera; microphone; …"on the iframe, nosandbox. - App camera permission granted before opening the flow.
-
onPermissionRequest(Android) /WKUIDelegate(iOS) implemented. -
mediaPlaybackRequiresUserGesture = false/mediaTypesRequiringUserActionForPlayback = []. - Hardware acceleration enabled.
- Default User-Agent preserved (token appended, never replaced) — verified with the snippet above.