Skip to main content

What are Verification Flows?

Verification flows define the complete user journey through the identity verification process. Each flow specifies:
  • Which documents to collect (passport, driver’s license, etc.)
  • Verification steps (selfie, liveness check, address verification)
  • User interface customization
  • Data validation rules
  • Compliance requirements
Flows are created and configured in your MetaMap Dashboard, allowing you to manage the verification experience without modifying your app code.

Basic Flow Implementation

Single Flow

The most common implementation uses a single verification flow:
import UIKit
import MetaMapSDK

class ViewController: UIViewController {
    
    @objc private func startVerification() {
        MetaMap.shared.showMetaMapFlow(
            clientId: "YOUR_CLIENT_ID",
            flowId: "YOUR_FLOW_ID",
            metadata: nil
        )
    }
}

extension ViewController: MetaMapButtonResultDelegate {
    func verificationSuccess(identityId: String?, verificationID: String?) {
        print("Verification completed successfully")
        print("Identity ID: \(identityId ?? "N/A")")
        print("Verification ID: \(verificationID ?? "N/A")")
    }
    
    func verificationCancelled() {
        print("User cancelled verification")
    }
}

Multi-Flow Support

The MetaMap iOS SDK supports multiple verification flows within a single application. This is useful when you need different verification levels for different user scenarios.

Use Cases for Multiple Flows

Tiered Verification

Basic verification for standard users, enhanced verification for premium accounts

Document Types

Different flows for different document types (passport vs. driver’s license)

Regional Compliance

Region-specific verification requirements based on user location

Re-verification

Simplified flow for users updating expired documents

Implementing Multiple Flows

import UIKit
import MetaMapSDK

class VerificationViewController: UIViewController {
    
    enum VerificationLevel {
        case basic
        case enhanced
        case document
        
        var flowId: String {
            switch self {
            case .basic:
                return "5e8zf446aa5b5e001a7769d0"
            case .enhanced:
                return "7e8zf446aa5b5e001a7769d1"
            case .document:
                return "9e8zf446aa5b5e001a7769d2"
            }
        }
    }
    
    private func startVerification(level: VerificationLevel) {
        MetaMap.shared.showMetaMapFlow(
            clientId: "YOUR_CLIENT_ID",
            flowId: level.flowId,
            metadata: [
                "verificationType": "\(level)",
                "timestamp": Date().timeIntervalSince1970
            ]
        )
    }
    
    @IBAction func basicVerificationTapped(_ sender: UIButton) {
        startVerification(level: .basic)
    }
    
    @IBAction func enhancedVerificationTapped(_ sender: UIButton) {
        startVerification(level: .enhanced)
    }
    
    @IBAction func documentOnlyTapped(_ sender: UIButton) {
        startVerification(level: .document)
    }
}

extension VerificationViewController: MetaMapButtonResultDelegate {
    func verificationSuccess(identityId: String?, verificationID: String?) {
        // Handle successful verification
        guard let identityId = identityId else { return }
        
        // Store the identity ID for future reference
        UserDefaults.standard.set(identityId, forKey: "metamap_identity_id")
        
        // Notify your backend
        notifyBackend(identityId: identityId, verificationID: verificationID)
    }
    
    func verificationCancelled() {
        // Handle cancellation
        showAlert(title: "Verification Cancelled", message: "Please complete verification to continue.")
    }
    
    private func notifyBackend(identityId: String, verificationID: String?) {
        // Implementation for backend notification
    }
    
    private func showAlert(title: String, message: String) {
        // Implementation for showing alert
    }
}

Objective-C Multi-Flow Example

#import "ViewController.h"
#import <MetaMapSDK/MetaMapSDK.h>

@interface ViewController () <MetaMapButtonResultDelegate>
@end

@implementation ViewController

- (void)startBasicVerification {
    [MetaMap.shared showMetaMapFlowWithClientId:@"YOUR_CLIENT_ID" 
                                         flowId:@"5e8zf446aa5b5e001a7769d0" 
                                       metadata:@{@"type": @"basic"}];
}

- (void)startEnhancedVerification {
    [MetaMap.shared showMetaMapFlowWithClientId:@"YOUR_CLIENT_ID" 
                                         flowId:@"7e8zf446aa5b5e001a7769d1" 
                                       metadata:@{@"type": @"enhanced"}];
}

#pragma mark - MetaMapButtonResultDelegate

- (void)verificationSuccessWithIdentityId:(NSString *)identityId 
                           verificationID:(NSString *)verificationID {
    NSLog(@"Verification Success: %@", identityId);
}

- (void)verificationCancelled {
    NSLog(@"Verification Cancelled");
}

@end

Flow Configuration

Flows are configured in your MetaMap Dashboard. Here are the key configuration options:

Document Collection

  • Document Types: Passport, national ID, driver’s license, residence permit
  • Document Countries: Specify which countries’ documents are accepted
  • Number of Documents: Single or multiple document collection

Verification Steps

  • Selfie Capture: Optional or required
  • Liveness Detection: Active or passive liveness checks
  • Address Verification: Proof of address document collection
  • Additional Data: Custom fields for collecting extra information

UI Customization

  • Branding: Custom colors, logos, and fonts
  • Language: Default language and available languages
  • Instructions: Custom text for each verification step
Use the metadata parameter to pass flow-specific configuration at runtime. This allows dynamic behavior without creating multiple flows.

Dynamic Flow Selection

You can select flows dynamically based on user attributes or business logic:
class VerificationManager {
    
    func selectFlow(for user: User) -> String {
        // Select flow based on user attributes
        if user.isPremium {
            return "enhanced_flow_id"
        } else if user.isReturning {
            return "reverification_flow_id"
        } else {
            return "standard_flow_id"
        }
    }
    
    func startVerification(for user: User) {
        let flowId = selectFlow(for: user)
        
        MetaMap.shared.showMetaMapFlow(
            clientId: "YOUR_CLIENT_ID",
            flowId: flowId,
            metadata: [
                "userId": user.id,
                "accountType": user.accountType,
                "country": user.country
            ]
        )
    }
}

Re-verification Flow

For users who need to update their verification (e.g., expired documents), pass the existing identityId in metadata:
func startReverification() {
    guard let existingIdentityId = UserDefaults.standard.string(forKey: "metamap_identity_id") else {
        // Start fresh verification
        startNewVerification()
        return
    }
    
    MetaMap.shared.showMetaMapFlow(
        clientId: "YOUR_CLIENT_ID",
        flowId: "YOUR_FLOW_ID",
        metadata: [
            "identityId": existingIdentityId  // Link to existing identity
        ]
    )
}
When re-verifying with an existing identityId, the new verification will be linked to the previous identity record in the MetaMap Dashboard, maintaining the user’s verification history.

Flow State Management

Track which flows users have completed:
class FlowTracker {
    
    enum FlowStatus: String {
        case notStarted
        case inProgress
        case completed
        case failed
    }
    
    func updateFlowStatus(flowId: String, status: FlowStatus, identityId: String?) {
        let key = "flow_\(flowId)_status"
        UserDefaults.standard.set(status.rawValue, forKey: key)
        
        if let identityId = identityId {
            UserDefaults.standard.set(identityId, forKey: "flow_\(flowId)_identityId")
        }
    }
    
    func getFlowStatus(flowId: String) -> FlowStatus {
        let key = "flow_\(flowId)_status"
        guard let statusString = UserDefaults.standard.string(forKey: key),
              let status = FlowStatus(rawValue: statusString) else {
            return .notStarted
        }
        return status
    }
}

extension ViewController: MetaMapButtonResultDelegate {
    func verificationSuccess(identityId: String?, verificationID: String?) {
        FlowTracker().updateFlowStatus(
            flowId: currentFlowId,
            status: .completed,
            identityId: identityId
        )
    }
}

Best Practices

Don’t create a one-size-fits-all flow. Different user scenarios may require different verification levels. Use multiple flows to optimize the user experience and compliance requirements.
Always test new flows in your MetaMap sandbox environment before deploying to production. This allows you to verify the user experience without affecting real users.
Pass relevant context in the metadata parameter to track which flows users complete and why. This data helps with analytics and debugging.
When you update a flow in the dashboard, existing SDK integrations will automatically use the new configuration. Test changes carefully to ensure backward compatibility.

Troubleshooting

Flow Not Loading

  • Verify the flow ID is correct
  • Ensure the flow is published (not in draft mode)
  • Check that the flow is compatible with your SDK version

Wrong Flow Appearing

  • Confirm you’re passing the correct flow ID for the scenario
  • Check for hardcoded flow IDs that should be dynamic
  • Verify flow selection logic is working as expected

Next Steps

Metadata

Learn how to customize flows with metadata

Delegates

Handle verification results with delegate callbacks

Build docs developers (and LLMs) love