Dashboard

Mobile App Drop-In

Add VerifyHuman verification to an existing iOS, Android, React Native, or Flutter app without building native verification screens.

Integration tier: Mobile App Drop-In sits between Hosted Flows (zero code, link or embed) and the Mobile API & SDK (full native integration).


When to use this

Use Mobile App Drop-In when:

Works well for: age gates, marketplace seller onboarding, event registration, pilot projects, teams without dedicated mobile engineers, and apps where speed-to-market matters more than fully native UX.


When to use the full Mobile SDK instead

Use the Mobile API & SDK when:


How it works

Your mobile app                    VerifyHuman
─────────────────────              ──────────────────────
1. Call your backend  ──────────▶  (your backend calls
                                    POST /hosted/generate
                                    or uses a VerifyFlow URL)

2. Receive hosted URL ◀──────────  hosted verification URL

3. Open URL in secure              /hosted/v/<token>
   browser sheet      ──────────▶  mobile-optimized
                                   step-based flow

4. User completes                  POST /hosted/complete/<token>
   verification       ◀──────────  result + redirect URL

5. App receives                    deep link / return URL
   deep link return   ◀──────────  (e.g. myapp://verify-done)

6. Your backend                    webhook: verification.completed
   gets the result    ◀──────────  (authoritative result)

The key principle: your app opens a URL, VerifyHuman handles everything inside it. Your backend receives the authoritative result by webhook.


  1. Your mobile app calls your own backend to start a verification.
  2. Your backend creates a VerifyHuman hosted verification URL (via VerifyFlow dashboard or the POST /hosted/generate API).
  3. Your app opens the hosted URL in a secure browser sheet.
  4. The user completes verification inside the VerifyHuman hosted flow — camera, liveness, step progression, retry screens, and privacy messaging are all handled.
  5. VerifyHuman finalizes the hosted session and calls POST /hosted/complete/<token> internally.
  6. Your backend receives the result by webhook (verification.completed event).
  7. VerifyHuman redirects the user back to your app using the configured return URL (which can be a deep link such as myapp://verify-done).
  8. When your app comes back to the foreground, it asks your backend for the latest verification status — your backend returns the result from the webhook.

Important: treat the deep link return as a UX signal only — it tells your app the user has returned, not that verification succeeded. Always confirm the result server-side through the webhook or a status check against your own backend.


Getting a hosted verification URL

  1. Go to your VerifyFlow dashboard and create or open a Flow.
  2. Publish the Flow to get a stable hosted page URL.
  3. Use the live hosted page URL as your verification URL.

Stable URL: VerifyFlow hosted page URLs do not change when you edit settings. The same URL works across all your mobile app users.

Option 2 — API (per-session tokens)

For per-user, single-use verification sessions, your backend can call:

POST /hosted/generate
Authorization: Bearer <your-api-key>
Content-Type: application/json

{
  "product": "verifyidentity",
  "redirect_url": "myapp://verify-complete?status=success",
  "failure_redirect_url": "myapp://verify-complete?status=failed",
  "expires_in_hours": 1
}

The response includes a hosted_url your backend returns to the app.


VerifyHuman supports two redirect URL fields on a hosted flow:

Field When used
redirect_url Verification passed — user is redirected here after success
failure_redirect_url Verification failed or user denied (e.g. age gate denial) — user is redirected here

Both can be:

There is no separate cancel_url field. If the user abandons the flow without completing it, no redirect fires — the user remains on the hosted page. Build a timeout/retry prompt in your app for this case.

Register a custom scheme in your Info.plist:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>

Handle the return in your AppDelegate or SceneDelegate:

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }
    if url.scheme == "myapp" && url.host == "verify-complete" {
        // User has returned. Ask your backend for the result.
        // Do NOT trust the URL query params as the verification result.
        NotificationCenter.default.post(name: .verificationDidReturn, object: nil)
    }
}

Register an intent filter in your AndroidManifest.xml:

<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="myapp" android:host="verify-complete" />
</intent-filter>

iOS

Recommended: SFSafariViewController

import SafariServices
import UIKit

final class VerificationViewController: UIViewController {
    func startVerification() {
        // Fetch this URL from your backend after creating a VerifyHuman hosted session.
        let hostedVerificationUrl = "https://app.verifyhuman.io/hosted/v/..."

        guard let url = URL(string: hostedVerificationUrl) else { return }

        let safari = SFSafariViewController(url: url)
        safari.dismissButtonStyle = .close
        present(safari, animated: true)
    }
}

SFSafariViewController inherits Safari's camera permission model, has access to the system keychain, and is visually trusted by users. Avoid WKWebView unless you have verified camera permission handling — raw WebViews frequently block camera access.

Also supported: ASWebAuthenticationSession when a browser-handoff authentication pattern is preferred (e.g. for apps that already use ASWebAuthenticationSession for OAuth).


Android (Kotlin)

Recommended: Chrome Custom Tabs

import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import android.content.Context

fun startVerification(context: Context, hostedVerificationUrl: String) {
    // Fetch this URL from your backend after creating a VerifyHuman hosted session.
    val intent = CustomTabsIntent.Builder()
        .setShowTitle(true)
        .build()

    intent.launchUrl(context, Uri.parse(hostedVerificationUrl))
}

Add the dependency to your build.gradle:

implementation "androidx.browser:browser:1.8.0"

Chrome Custom Tabs use the device's Chrome instance and its camera permission model. Avoid raw android.webkit.WebView — camera permissions are blocked by default unless explicitly granted.


React Native

import { Linking } from "react-native";

export async function startVerification(hostedVerificationUrl) {
  // Fetch this URL from your backend after creating a VerifyHuman hosted session.
  const supported = await Linking.canOpenURL(hostedVerificationUrl);

  if (!supported) {
    throw new Error("Unable to open verification URL");
  }

  await Linking.openURL(hostedVerificationUrl);
}

For a better in-app experience, use react-native-inappbrowser-reborn which wraps SFSafariViewController and Chrome Custom Tabs:

import InAppBrowser from 'react-native-inappbrowser-reborn';

export async function startVerification(hostedVerificationUrl) {
  // Fetch this URL from your backend after creating a VerifyHuman hosted session.
  if (await InAppBrowser.isAvailable()) {
    const result = await InAppBrowser.openAuth(hostedVerificationUrl, 'myapp://verify-complete');
    // result.type === 'success' if the user returned via deep link
    // Always confirm result by asking your backend — do not trust the redirect alone.
  } else {
    Linking.openURL(hostedVerificationUrl);
  }
}

Flutter

import 'package:url_launcher/url_launcher.dart';

Future<void> startVerification(String hostedVerificationUrl) async {
  // Fetch this URL from your backend after creating a VerifyHuman hosted session.
  final uri = Uri.parse(hostedVerificationUrl);

  if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
    throw Exception('Unable to open verification URL');
  }
}

Add to pubspec.yaml:

dependencies:
  url_launcher: ^6.3.0

For in-app browser control, use LaunchMode.inAppBrowserView (Android) or LaunchMode.inAppWebView only if you have confirmed camera permissions work on your target devices.


Result delivery

Configure a webhook endpoint in your dashboard to receive verification.completed events. Your backend receives:

{
  "event": "verification.completed",
  "verification_id": "...",
  "status": "PASS",
  "product": "verifyidentity",
  "environment": "live",
  "timestamp": "2026-05-17T10:30:00Z"
}

See the Webhooks guide for signing secret validation and retry behavior.

Status polling fallback

VerifyHuman does not currently expose a per-hosted-session status polling endpoint. If your app returns via deep link before the webhook arrives, show a loading state and poll your own backend, which should return the result once the webhook has been received and processed.

A typical customer backend pattern:

GET /your-backend/api/verify-status?session_ref=<your_session_id>
→ { "status": "pending" | "pass" | "fail" }

Your backend updates this status when it receives the VerifyHuman webhook.


Test mode

Each VerifyFlow Flow has a test hosted page URL alongside the live URL. Use the test URL during development:

Find both URLs in your Flow detail page after publishing.


Security recommendations


User experience recommendations