Skip to content

Mobile & Flutter Integration ​

Integrate YeboVerify into your mobile apps for seamless KYC verification.

Overview ​

YeboVerify uses a web-based verification flow that works with any mobile platform:

  1. Your app calls our API to create a verification session
  2. Open the verification URL in a browser/WebView
  3. User completes verification (ID + selfie)
  4. Webhook notifies your backend of the result
  5. User returns to your app

Quick Start ​

1. Create Session (Backend) ​

javascript
// Node.js backend
const response = await fetch('https://yeboverify-api.run.app/v1/sessions/create', {
  method: 'POST',
  headers: {
    'X-API-Key': 'yv_live_YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    externalRef: 'user_123',           // Your user ID
    redirectUrl: 'yourapp://verify',   // Deep link back to app
  }),
});

const { verifyUrl, sessionToken } = await response.json();
// verifyUrl: https://verify.yeboverify.com#session=xxx

2. Open Verification (Mobile) ​

dart
// Flutter
import 'package:url_launcher/url_launcher.dart';

await launchUrl(
  Uri.parse(verifyUrl),
  mode: LaunchMode.externalApplication,
);
swift
// iOS
UIApplication.shared.open(URL(string: verifyUrl)!)
kotlin
// Android
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(verifyUrl))
startActivity(intent)
dart
// Flutter - Handle deep link
// In your app's route handler:
if (uri.path == '/verify') {
  final status = uri.queryParameters['status'];
  if (status == 'success') {
    // Verification completed - fetch updated user
    await refreshUser();
  }
}

4. Receive Webhook (Backend) ​

javascript
// Your webhook endpoint
app.post('/webhooks/yeboverify', (req, res) => {
  const { event, externalRef, decision, extractedData } = req.body;
  
  if (event === 'verification.completed') {
    if (decision === 'approved') {
      // Update user as verified
      await updateUser(externalRef, {
        kycVerified: true,
        kycName: `${extractedData.names} ${extractedData.surname}`,
        kycCountry: extractedData.issuingCountry,
      });
    }
  }
  
  res.json({ received: true });
});

Flutter Widget Library ​

For Flutter apps, we provide pre-built widgets that handle the entire flow.

Installation ​

yaml
# pubspec.yaml
dependencies:
  yeboverify_flutter:
    git:
      url: https://github.com/omegathesecond/yeboverify-flutter.git

YeboVerifyButton ​

One-tap verification button:

dart
YeboVerifyButton(
  apiKey: 'yv_live_YOUR_API_KEY',
  externalRef: currentUser.id,
  
  onSuccess: (VerificationResult result) {
    print('Verified: ${result.extractedData.names}');
    refreshUser();
  },
  
  onError: (String error) {
    showSnackbar(error);
  },
  
  // Customization
  text: 'Verify Identity',
  style: YeboVerifyButtonStyle.filled,
)

YeboVerifyBadge ​

Display verification status:

dart
YeboVerifyBadge(
  isVerified: user.kycVerified,
  size: 24,
)

YeboVerifyCard ​

Full verification card with status and action:

dart
YeboVerifyCard(
  isVerified: user.kycVerified,
  verifiedAt: user.kycVerifiedAt,
  country: user.kycCountry,
  
  onVerify: () async {
    final session = await createSession(user.id);
    launchUrl(Uri.parse(session.verifyUrl));
  },
)

React Native ​

Create Session ​

javascript
const startVerification = async (userId) => {
  const response = await fetch(`${API_URL}/kyc/start`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${userToken}`,
    },
  });
  
  const { verifyUrl } = await response.json();
  
  // Open in browser
  await Linking.openURL(verifyUrl);
};
javascript
// App.js
useEffect(() => {
  const handleDeepLink = ({ url }) => {
    if (url.includes('/verify')) {
      const params = new URLSearchParams(url.split('?')[1]);
      if (params.get('status') === 'success') {
        refreshUser();
      }
    }
  };
  
  Linking.addEventListener('url', handleDeepLink);
  return () => Linking.removeEventListener('url', handleDeepLink);
}, []);

iOS (Swift) ​

Start Verification ​

swift
func startVerification() async throws {
    let url = URL(string: "\(apiUrl)/v1/sessions/create")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue(apiKey, forHTTPHeaderField: "X-API-Key")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    
    let body = ["externalRef": userId, "redirectUrl": "myapp://verify"]
    request.httpBody = try JSONEncoder().encode(body)
    
    let (data, _) = try await URLSession.shared.data(for: request)
    let result = try JSONDecoder().decode(SessionResponse.self, from: data)
    
    await UIApplication.shared.open(URL(string: result.verifyUrl)!)
}

Handle Return ​

swift
// SceneDelegate.swift
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }
    
    if url.path == "/verify" {
        let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
        let status = components?.queryItems?.first(where: { $0.name == "status" })?.value
        
        if status == "success" {
            NotificationCenter.default.post(name: .verificationComplete, object: nil)
        }
    }
}

Android (Kotlin) ​

Start Verification ​

kotlin
suspend fun startVerification(userId: String) {
    val response = apiService.createSession(
        CreateSessionRequest(
            externalRef = userId,
            redirectUrl = "myapp://verify"
        )
    )
    
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(response.verifyUrl))
    startActivity(intent)
}

Handle Return ​

kotlin
// In your Activity
override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    
    intent?.data?.let { uri ->
        if (uri.path == "/verify") {
            val status = uri.getQueryParameter("status")
            if (status == "success") {
                viewModel.refreshUser()
            }
        }
    }
}

AndroidManifest.xml ​

xml
<activity android:name=".MainActivity">
    <intent-filter>
        <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" />
    </intent-filter>
</activity>

Session Options ​

javascript
const session = await createSession({
  externalRef: 'user_123',              // Required: Your user ID
  
  // Optional
  redirectUrl: 'myapp://verify',        // Deep link after verification
  webhookUrl: 'https://api.you.com/webhook',  // Override default webhook
  metadata: {                           // Custom data (returned in webhook)
    plan: 'premium',
    source: 'mobile_app'
  },
  
  // UX options
  language: 'en',                       // UI language
  theme: 'dark',                        // dark or light
});

Webhook Payload ​

json
{
  "event": "verification.completed",
  "verificationId": "ver_abc123",
  "externalRef": "user_123",
  "decision": "approved",
  "confidence": "high",
  "faceScore": 99.72,
  "ocrConfidence": 95.5,
  "extractedData": {
    "surname": "DLAMINI",
    "names": "SIPHO DAVID",
    "dateOfBirth": "1990-05-15",
    "sex": "Male",
    "idNumber": "9005155555083",
    "nationality": "Swazi",
    "issuingCountry": "Eswatini",
    "documentType": "National ID",
    "issueDate": "2020-01-15",
    "expiryDate": "2030-01-15"
  },
  "timestamp": "2026-03-27T01:00:00Z"
}

Best Practices ​

1. Always Use Backend Sessions ​

Never expose your API key in mobile apps. Create sessions from your backend.

dart
// ❌ Bad - API key in app
final session = await YeboVerify.createSession(apiKey: 'yv_live_xxx');

// ✅ Good - Call your backend
final session = await yourApi.startKyc(userId);

2. Handle All States ​

dart
switch (verificationStatus) {
  case 'success':
    showSuccess('Verification complete!');
    break;
  case 'failed':
    showError('Verification failed. Please try again.');
    break;
  case 'cancelled':
    // User closed without completing
    break;
  case 'expired':
    showError('Session expired. Please try again.');
    break;
}

3. Implement Webhooks ​

Don't rely only on redirect status. Always verify via webhook.

javascript
// Your backend
app.post('/webhooks/yeboverify', async (req, res) => {
  // Verify the replay-resistant V2 signature (X-YeboVerify-Signature-V2 +
  // X-YeboVerify-Timestamp) — see /webhooks for the full verifyWebhookSignatureV2
  // implementation. Do not verify against the legacy X-YeboVerify-Signature
  // alone; it has no replay protection.
  const timestamp = req.headers['x-yeboverify-timestamp'];
  const signatureV2 = req.headers['x-yeboverify-signature-v2'];
  if (!verifyWebhookSignatureV2(req.body, timestamp, signatureV2, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Process webhook
  const { event, externalRef, decision } = req.body;
  // ...
});

4. Show Progress ​

dart
YeboVerifyButton(
  onPressed: () async {
    setState(() => isLoading = true);
    try {
      final session = await api.startKyc(userId);
      await launchUrl(Uri.parse(session.verifyUrl));
    } finally {
      setState(() => isLoading = false);
    }
  },
  child: isLoading 
    ? CircularProgressIndicator() 
    : Text('Verify Identity'),
)

Business (KYB) Verification from Mobile ​

If your app has a business-onboarding flow (e.g. a merchant/vendor signing up), the same launchUrl / hosted-browser pattern above applies — just create the verification with POST /v1/company-verifications on your backend instead of /v1/sessions/create, and send the authorized signatory to the returned verifyUrl. They complete the identical ID + selfie capture; your backend uploads the company's own documents (registration certificate, proof of address, etc.) separately via the API. See Company Verification (KYB) for the full flow.


Support ​

Identity Verification API for Africa