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:
- You already have a mobile app.
- You want to launch verification quickly without building native camera, liveness, retry, privacy, and completion screens.
- You want VerifyHuman to host the entire verification UX.
- You want results delivered to your backend through webhook or API.
- You need a working pilot in hours, not days.
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:
- You need fully native UI matched to your app's design system.
- You want deep camera control or custom capture screens.
- You need native analytics or app-level session handling.
- Your app requires offline-capable capture with deferred upload.
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.
Recommended architecture
- Your mobile app calls your own backend to start a verification.
- Your backend creates a VerifyHuman hosted verification URL (via VerifyFlow dashboard or the
POST /hosted/generateAPI). - Your app opens the hosted URL in a secure browser sheet.
- The user completes verification inside the VerifyHuman hosted flow — camera, liveness, step progression, retry screens, and privacy messaging are all handled.
- VerifyHuman finalizes the hosted session and calls
POST /hosted/complete/<token>internally. - Your backend receives the result by webhook (
verification.completedevent). - VerifyHuman redirects the user back to your app using the configured return URL (which can be a deep link such as
myapp://verify-done). - 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
Option 1 — VerifyFlow (no-code, recommended for most teams)
- Go to your VerifyFlow dashboard and create or open a Flow.
- Publish the Flow to get a stable hosted page URL.
- 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.
Redirect / deep link return URLs
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:
- A web URL:
https://yourapp.com/verified - A deep link:
myapp://verify-complete?result=pass - A universal link / App Link:
https://yourapp.com/app/verified
There is no separate
cancel_urlfield. 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.
iOS deep link example
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)
}
}
Android deep link example
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>
Recommended browser containers
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
Webhook (recommended)
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:
- No live credits are consumed.
- Verification results are synthetic.
- A yellow "Test mode" banner is shown in the hosted flow.
Find both URLs in your Flow detail page after publishing.
Security recommendations
- Create sessions from your backend. Never expose your VerifyHuman private API key in the mobile app binary.
- Use HTTPS hosted URLs. All VerifyHuman hosted pages are HTTPS-only.
- Use short-lived sessions for per-user verification (e.g. 1-hour TTL via
expires_in_hours). - Verify results server-side. Treat deep link returns as UX signals, not proof of verification. Always confirm through webhook or your backend status endpoint.
- Use universal links / App Links where possible for more secure deep link handling than custom URL schemes.
- Use SFSafariViewController / Chrome Custom Tabs — they have better camera permission behavior and security isolation than raw WebViews.
User experience recommendations
- Launch the flow from a clear CTA such as "Verify now" or "Start identity check."
- Tell users what is about to happen: "You'll briefly open a secure verification screen."
- Return users to a meaningful app screen after verification (not just the home screen).
- Show a loading state while waiting for webhook/status confirmation.
- Provide a retry path if the user returns without completing verification.
Related
- Hosted Flows Guide — full Hosted Flow configuration reference
- Mobile API & SDK — full native integration for advanced use cases
- Webhooks — result delivery
- Test vs Live environments
- Mobile App Drop-In — Backend API Pattern