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