Android WebView requirements

Use this guide when your Android app opens an ID.me OAuth 2.0 verification workflow in a WebView. Android integrations must handle camera permission checks before handing ID.me document upload, selfie capture, or liveness capture to native camera functionality.

Verification stages

Document upload starts when the user clicks a document upload or Take Photo button. If live document capture is required, the app must request camera permission before launching MediaStore.ACTION_IMAGE_CAPTURE.

Selfie and liveness capture also require camera permission. There is no gallery-upload alternative for these steps, so permission denial must keep the user in a recoverable state with a retry or settings path.

Required permission

AndroidManifest.xml
1<uses-permission android:name="android.permission.CAMERA" />

Document upload

ID.me pages use file inputs for document capture. Android WebView delegates those inputs to the native app through WebChromeClient.onShowFileChooser.

Check camera permission every time before opening native camera capture. If permission is denied, do not launch the camera intent.

Android WebView file chooser
1class VerificationActivity : AppCompatActivity() {
2 private var pendingFileChooser: ValueCallback<Array<Uri>>? = null
3 private var cameraOutputUri: Uri? = null
4
5 override fun onCreate(savedInstanceState: Bundle?) {
6 super.onCreate(savedInstanceState)
7
8 webView.webChromeClient = object : WebChromeClient() {
9 override fun onShowFileChooser(
10 webView: WebView,
11 filePathCallback: ValueCallback<Array<Uri>>,
12 fileChooserParams: FileChooserParams
13 ): Boolean {
14 pendingFileChooser?.onReceiveValue(null)
15 pendingFileChooser = filePathCallback
16
17 return if (hasCameraPermission()) {
18 launchDocumentCamera()
19 true
20 } else {
21 requestCameraPermission()
22 true
23 }
24 }
25 }
26 }
27
28 private fun hasCameraPermission(): Boolean {
29 return ContextCompat.checkSelfPermission(
30 this,
31 Manifest.permission.CAMERA
32 ) == PackageManager.PERMISSION_GRANTED
33 }
34
35 private fun requestCameraPermission() {
36 ActivityCompat.requestPermissions(
37 this,
38 arrayOf(Manifest.permission.CAMERA),
39 REQUEST_CAMERA_PERMISSION
40 )
41 }
42
43 override fun onRequestPermissionsResult(
44 requestCode: Int,
45 permissions: Array<out String>,
46 grantResults: IntArray
47 ) {
48 super.onRequestPermissionsResult(requestCode, permissions, grantResults)
49
50 if (requestCode != REQUEST_CAMERA_PERMISSION) return
51
52 if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
53 launchDocumentCamera()
54 } else {
55 handleCameraPermissionDenied()
56 }
57 }
58}

Permission denial and settings recovery

If the user denies camera permission, cancel the pending file chooser callback and keep the user in the WebView. If Android reports that the permission can still be requested, show an explanation and let the user retry. If the permission is permanently denied, show an Open Settings action. Run this check on each camera entry point, including after the user disables camera access in app settings and starts a new verification session.

ID.me may also show a permission guard inside the WebView when camera access is unavailable. This guard prevents the page from handing off to native camera capture when permission is blocked, but the host app still needs to provide the settings recovery path.

Android WebView alert shown by ID.me when camera permission is unavailable. The alert says camera permissions are required and asks the user to enable camera permissions for the app in Settings.

Android camera permission result
1private fun handleCameraPermissionDenied() {
2 pendingFileChooser?.onReceiveValue(null)
3 pendingFileChooser = null
4
5 val canAskAgain = ActivityCompat.shouldShowRequestPermissionRationale(
6 this,
7 Manifest.permission.CAMERA
8 )
9
10 if (canAskAgain) {
11 showCameraRetryDialog(
12 onRetry = { requestCameraPermission() },
13 onCancel = { /* Keep the user in the verification workflow. */ }
14 )
15 } else {
16 showCameraSettingsDialog(
17 onOpenSettings = {
18 waitingForCameraSettingsReturn = true
19 openAppSettings()
20 },
21 onCancel = { /* Keep the user in the verification workflow. */ }
22 )
23 }
24}

Open the app’s settings page with Settings.ACTION_APPLICATION_DETAILS_SETTINGS.

Android app settings
1fun openAppSettings() {
2 val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
3 data = Uri.fromParts("package", packageName, null)
4 }
5 startActivity(intent)
6}
7
8override fun onResume() {
9 super.onResume()
10
11 if (waitingForCameraSettingsReturn && hasCameraPermission()) {
12 waitingForCameraSettingsReturn = false
13 launchDocumentCamera()
14 }
15}

If your app exposes an ID.me WebView bridge, let the page request the same settings action.

Android WebView settings bridge
1class IDmeNativeBridge(private val activity: VerificationActivity) {
2 @JavascriptInterface
3 fun openAppSettings(permission: String) {
4 if (permission == "camera") {
5 activity.runOnUiThread {
6 activity.openAppSettings()
7 }
8 }
9 }
10}
11
12webView.settings.javaScriptEnabled = true
13webView.addJavascriptInterface(IDmeNativeBridge(this), "IDmeNative")

Only expose JavaScript interfaces from trusted WebView origins. Do not add broad native bridges to untrusted content.

Camera output URI grants

When your app captures a document image into an output URI, grant URI access explicitly.

Android URI grants
1private fun launchDocumentCamera() {
2 val outputUri = createDocumentImageUri()
3 cameraOutputUri = outputUri
4
5 val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
6 putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
7 addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
8 addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
9 clipData = ClipData.newUri(
10 contentResolver,
11 "ID.me document capture",
12 outputUri
13 )
14 }
15
16 startActivityForResult(intent, REQUEST_DOCUMENT_CAPTURE)
17}
18
19override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
20 super.onActivityResult(requestCode, resultCode, data)
21
22 if (requestCode != REQUEST_DOCUMENT_CAPTURE) return
23
24 val result = if (resultCode == RESULT_OK && cameraOutputUri != null) {
25 arrayOf(cameraOutputUri!!)
26 } else {
27 null
28 }
29
30 pendingFileChooser?.onReceiveValue(result)
31 pendingFileChooser = null
32 cameraOutputUri = null
33}

Android logs may warn that implicit URI read and write grants for image capture will be discontinued in future Android versions. Set read and write grants explicitly and provide ClipData for the output URI.

Liveness and selfie capture

Start selfie or liveness capture only after camera permission is granted.

Android liveness permission gate
1private fun startLivenessWhenReady() {
2 if (hasCameraPermission()) {
3 launchIdmeLiveness()
4 } else {
5 requestCameraPermission()
6 }
7}

Testing checklist

  • Grant camera permission and complete document upload.
  • Deny camera permission once, retry, grant permission, and complete document upload.
  • Permanently deny camera permission and confirm the app settings path works.
  • Disable camera permission in app settings, return to a new verification session, and confirm the app re-checks permission before opening capture.
  • Return from app settings, re-check camera permission, and continue capture only when permission is granted.
  • Confirm any WebView settings bridge is restricted to trusted ID.me pages.
  • Confirm the camera output URI has explicit read and write grants.
  • Complete selfie or liveness capture after granting permission.
  • Deny permission before selfie or liveness and confirm the app does not start capture.