WebView Camera Prerequisites
This page is for client mobile app developers who embed the VIDA Liveness Web SDK - or a VIDA-hosted verification page — inside an in-app WebView. It explains why the camera may fail to load in a WebView and how to make the app work.
Overview
The SDK uses the standard browser API navigator.mediaDevices.getUserMedia() to access the camera. Inside an in-app WebView, that call only succeeds when all three of the following are true:
The app has been granted OS camera permission.
The app forwards and grants the WebView's camera request in native code. This is the step most often missing.
The page is served over a secure context (HTTPS).
Minimum WebView / Engine Version
Beyond permissions, the WebView engine has capability to run the SDK's face-detection engine (MediaPipe), which requires WebGL2 and WebAssembly.
Android: Android System WebView / Chromium ≥ 101.
iOS: the WebView uses the system WebKit, which stays current with the OS. iOS 14.3+ is required for
getUserMediainWKWebView.
Android 8 / 9
~58–69
Limited (Chromium dropped support after v109)
Android 10
~77
Yes - update via Play Store
Android 11
~83–85
Yes
Android 12+
~94+
Yes
Why this matters
Android System WebView updates separately from the OS. It is a Play Store component, so its version is independent of the Android version — a device on Android 10 shipped with WebView ~77 (released 2019) and stays there until someone updates it.
When the WebView is too old, the failure is easy to misread:
It cannot load WebGL2 / WASM, and may not reach the SDK's model/asset CDN.
The flow gets stuck on the loading spinner — the camera never opens.
No telemetry is captured for the session, so support sees an empty log.
The two failure modes look different, which lets you tell them apart at a glance:
Loading spinner forever, then fails
Outdated WebView engine (< v101)
No error code; empty logs
How to check and update it (device side)
Open the Play Store → search "Android System WebView" → Update. Also update Chrome, which provides the WebView engine on some devices.
Or verify the installed version: Settings → Apps → Android System WebView.
After updating, fully close and reopen the host app, then retry the flow.
The three permission layers
Granting permission at one layer does not propagate to the others.
OS / app permission
Android / iOS
User grants the host app camera access (runtime prompt / Settings).
WebView delegation
Your native app code
The app must explicitly handle and grant the WebView's camera permission request. There is no default behavior on Android.
Origin / site permission
The WebView engine
Result of getUserMedia() after layer 2 allows it.
Android (android.webkit.WebView)
1. Declare and request the OS camera permission
In AndroidManifest.xml:
Request the runtime permission before loading the page (Android 6+):
2. Grant the WebView's permission request (the critical step)
Set a WebChromeClient and handle onPermissionRequest. Without this, the WebView denies the camera by default:
3. Enable the required WebView settings
4. Serve over HTTPS
getUserMedia requires a secure context. Load the SDK/hosted page over https:// (not http://, and not a file:// page making cross-origin calls). Do not disable TLS validation.
Common Android pitfalls
No
WebChromeClient.onPermissionRequestoverride → camera always denied. This is the most common cause.Granting the WebView request before the app has the OS permission → still denied. Request OS permission first.
mediaPlaybackRequiresUserGesture = true(the default) can block the stream from starting.Using a heavily customized or outdated WebView (System WebView not updated) that lacks WebRTC support.
iOS (WKWebView)
1. Declare the usage description
In Info.plist:
The OS prompts the user the first time the camera is accessed. Without this key, the app crashes on access.
2. Configure WKWebView for inline media capture
3. (iOS 15+) Auto-grant the WebView capture prompt if desired
On iOS 15+ you can implement WKUIDelegate to control the per-page capture permission instead of showing the WebView's own prompt:
4. Serve over HTTPS
Same secure-context requirement as Android. Avoid ATS exceptions that downgrade security.
Common iOS pitfalls
Using
UIWebViewinstead ofWKWebView→ camera never works.Missing
NSCameraUsageDescription→ app crashes when the camera is accessed.allowsInlineMediaPlayback = false, or requiring a user gesture → the stream won't start.The user previously denied OS camera permission to the app → it must be re-enabled in Settings → [App] → Camera. The WebView cannot override this.
Full sample code
Complete, drop-in samples with all required settings applied. Replace the placeholder URL with the VIDA flow / hosted link you were given.
1. Android
Changes vs. the basic snippets: added setMediaPlaybackRequiresUserGesture(false) (without it the camera stream can be blocked from starting), and onPermissionRequest now grants only RESOURCE_VIDEO_CAPTURE instead of every requested resource.
AndroidManifest.xml must also declare:
2. iOS
Changes vs. the basic snippets: added mediaTypesRequiringUserActionForPlayback = [] (the iOS equivalent of Android's media-gesture setting) and the iOS 15+ capture-permission delegate.
Info.plist must also declare:
3. React Native
Use the react-native-webview package (v11+). The same three layers apply — the JS props below cover the WebView delegation and settings, but you still need the native OS permission and the manifest / Info.plist entries.
mediaPlaybackRequiresUserAction={false}
Allow the camera stream to start without an extra tap (Android + iOS).
allowsInlineMediaPlayback
Inline (non-fullscreen) capture on iOS.
mediaCapturePermissionGrantType="grant"
Auto-grant the getUserMedia prompt on iOS 15+.
javaScriptEnabled, domStorageEnabled
JS + localStorage (SDK requirement).
You must also add the native entries:
Android
android/app/src/main/AndroidManifest.xml:<uses-permission android:name="android.permission.CAMERA" />iOS
ios/<App>/Info.plist:NSCameraUsageDescription(a string explaining camera use).
4. Flutter (flutter_inappwebview)
For camera / getUserMedia inside a Flutter WebView, use flutter_inappwebview (v6+) — it exposes the onPermissionRequest callback and media settings needed for the camera. (The official webview_flutter works too; see the note below.) Pair it with permission_handler for the OS-level prompt.
mediaPlaybackRequiresUserGesture: false
Allow the camera stream to start without a tap (Android + iOS).
allowsInlineMediaPlayback: true
Inline capture on iOS.
onPermissionRequest → GRANT
Grant the WebView's camera request (Android + iOS 15+).
iframeAllow: "camera; microphone"
Needed if the SDK runs inside an iframe on the page.
You must also add the native entries:
Android
android/app/src/main/AndroidManifest.xml:<uses-permission android:name="android.permission.CAMERA" />iOS
ios/Runner/Info.plist:NSCameraUsageDescription(a string explaining camera use).
Using the official webview_flutter instead? On Android you must grant the request explicitly:
And create the iOS controller with allowsInlineMediaPlayback: true + mediaTypesRequiringUserAction: {}.
How to tell which layer is failing
SDK shows error 70001 immediately; no OS prompt ever appeared
Layer 2 (Android onPermissionRequest missing / iOS delegate denying)
Implement the native permission handler.
OS camera prompt appeared, user allowed, still fails
Layer 2 or secure context
Verify grant() is called for RESOURCE_VIDEO_CAPTURE; confirm HTTPS.
Stuck on the loading spinner; camera preview never appears, then fails. No logs captured for the session.
Outdated WebView engine (< v101) — can't load WebGL2/WASM or reach the model CDN
Update Android System WebView to ≥ 101 via Play Store (see Minimum WebView / engine version).
Works in mobile Safari/Chrome but not in the app
WebView config
Compare against the settings above (WKWebView / WebChromeClient).
Error 70003 (not supported) / 70005 (no camera)
Engine / device
Old WebView without WebRTC, or no camera device.
Worked once, now skips straight to a denied error
SDK caches an "allowed" flag in localStorage independent of real permission
Clear WebView storage, or re-grant in OS settings then reload.
Last updated
