Skip to main content

Overview

The Vouch iOS SDK provides email validation and device fingerprinting for native iOS applications. Built with Swift, it integrates seamlessly with both SwiftUI and UIKit.
Package: VouchSDK Platform: iOS 15.0+ Language: Swift 5.9+ Distribution: Swift Package Manager

Installation

Via Xcode

  1. Open your project in Xcode
  2. Go to File → Add Package Dependencies
  3. Enter the repository URL: https://github.com/Vouch-IN/vouch-sdk-ios
  4. Select version requirements: “Up to Next Major Version” → 0.1.7
  5. Add VouchSDK to your target

Via Package.swift

Add the package dependency to your Package.swift:
Package.swift
dependencies: [
    .package(url: "https://github.com/Vouch-IN/vouch-sdk-ios", from: "0.1.7")
]
Then add it to your target dependencies:
.target(
    name: "YourTarget",
    dependencies: ["VouchSDK"]
)

Quick Start

import VouchSDK

// Initialize the SDK
let vouch = Vouch(
    projectId: "your-project-id",
    apiKey: "your-client-api-key"
)

// Validate an email
Task {
    do {
        let result = try await vouch.validate("[email protected]")

        // Check the validation data
        if let data = result.data {
            switch data {
            case .validation(let validationData):
                print("✓ Email validated:", result.email ?? "")
                print("Recommendation:", validationData.recommendation.rawValue)
                print("Signals:", validationData.signals)

            case .error(let errorData):
                print("✗ Error:", errorData.error)
                print("Message:", errorData.message)
            }
        }
    } catch {
        print("Request error:", error)
    }
}

SwiftUI Integration

Basic Form Validation

import SwiftUI
import VouchSDK

struct SignUpView: View {
    @State private var email = ""
    @State private var isValidating = false
    @State private var validationMessage: String?
    @State private var isValid = false

    let vouch = Vouch(
        projectId: "your-project-id",
        apiKey: "your-client-api-key"
    )

    var body: some View {
        VStack(spacing: 20) {
            TextField("Email Address", text: $email)
                .textFieldStyle(.roundedBorder)
                .autocapitalization(.none)
                .keyboardType(.emailAddress)
                .textContentType(.emailAddress)

            Button("Sign Up") {
                validateEmail()
            }
            .disabled(isValidating || email.isEmpty)

            if let message = validationMessage {
                Text(message)
                    .foregroundColor(isValid ? .green : .red)
                    .font(.caption)
            }

            if isValidating {
                ProgressView()
            }
        }
        .padding()
    }

    func validateEmail() {
        isValidating = true
        validationMessage = nil

        Task {
            do {
                let result = try await vouch.validate(email)

                await MainActor.run {
                    isValidating = false

                    if let data = result.data {
                        switch data {
                        case .validation(let validationData):
                            isValid = true
                            validationMessage = "✓ Valid (\(validationData.recommendation.rawValue))"
                            // Proceed with sign-up
                        case .error(let errorData):
                            isValid = false
                            validationMessage = errorData.message
                        }
                    } else {
                        isValid = false
                        validationMessage = result.error ?? "Email validation failed"
                    }
                }
            } catch {
                await MainActor.run {
                    isValidating = false
                    isValid = false
                    validationMessage = "Error: \(error.localizedDescription)"
                }
            }
        }
    }
}

Real-Time Validation

import SwiftUI
import VouchSDK
import Combine

struct RealtimeEmailField: View {
    @State private var email = ""
    @State private var validationState: ValidationState = .idle
    @State private var cancellable: AnyCancellable?

    let vouch = Vouch(projectId: "your-project-id", apiKey: "your-client-api-key")

    enum ValidationState {
        case idle
        case validating
        case valid
        case invalid(String)
    }

    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                TextField("Email", text: $email)
                    .textFieldStyle(.roundedBorder)
                    .onChange(of: email) { _, newValue in
                        scheduleValidation(email: newValue)
                    }

                switch validationState {
                case .idle:
                    EmptyView()
                case .validating:
                    ProgressView()
                        .scaleEffect(0.7)
                case .valid:
                    Image(systemName: "checkmark.circle.fill")
                        .foregroundColor(.green)
                case .invalid:
                    Image(systemName: "xmark.circle.fill")
                        .foregroundColor(.red)
                }
            }

            if case .invalid(let message) = validationState {
                Text(message)
                    .font(.caption)
                    .foregroundColor(.red)
            }
        }
    }

    func scheduleValidation(email: String) {
        cancellable?.cancel()

        guard !email.isEmpty else {
            validationState = .idle
            return
        }

        validationState = .validating

        cancellable = Just(email)
            .delay(for: .milliseconds(500), scheduler: RunLoop.main)
            .sink { emailToValidate in
                Task {
                    do {
                        let result = try await vouch.validate(emailToValidate)

                        await MainActor.run {
                            if let data = result.data {
                                switch data {
                                case .validation(let validationData):
                                    if validationData.recommendation == .allow {
                                        validationState = .valid
                                    } else {
                                        validationState = .invalid("Email flagged: \(validationData.recommendation.rawValue)")
                                    }
                                case .error(let errorData):
                                    validationState = .invalid(errorData.message)
                                }
                            } else {
                                validationState = .invalid(result.error ?? "Invalid email")
                            }
                        }
                    } catch {
                        await MainActor.run {
                            validationState = .invalid(error.localizedDescription)
                        }
                    }
                }
            }
    }
}

UIKit Integration

Basic Usage

import UIKit
import VouchSDK

class SignUpViewController: UIViewController {
    let vouch = Vouch(
        projectId: "your-project-id",
        apiKey: "your-client-api-key"
    )

    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var signUpButton: UIButton!
    @IBOutlet weak var resultLabel: UILabel!

    @IBAction func signUpButtonTapped(_ sender: UIButton) {
        guard let email = emailTextField.text, !email.isEmpty else {
            resultLabel.text = "Please enter an email address"
            resultLabel.textColor = .systemRed
            return
        }

        signUpButton.isEnabled = false
        resultLabel.text = "Validating..."
        resultLabel.textColor = .systemGray

        Task {
            do {
                let result = try await vouch.validate(email)

                await MainActor.run {
                    signUpButton.isEnabled = true

                    if let data = result.data {
                        switch data {
                        case .validation(let validationData):
                            let recommendation = validationData.recommendation.rawValue
                            resultLabel.text = "✓ Valid (\(recommendation))"
                            resultLabel.textColor = .systemGreen
                            // Proceed with sign-up
                        case .error(let errorData):
                            resultLabel.text = errorData.message
                            resultLabel.textColor = .systemRed
                        }
                    } else {
                        resultLabel.text = result.error ?? "Invalid email"
                        resultLabel.textColor = .systemRed
                    }
                }
            } catch {
                await MainActor.run {
                    signUpButton.isEnabled = true
                    resultLabel.text = "Error: \(error.localizedDescription)"
                    resultLabel.textColor = .systemRed
                }
            }
        }
    }
}

Configuration

Custom Options

import VouchSDK

let options = VouchOptions(
    endpoint: "https://api.vouch.expert",
    version: .version(1)  // Use /v1/ endpoint
)

let vouch = Vouch(
    projectId: "your-project-id",
    apiKey: "your-client-api-key",
    options: options
)

API Version

Control which API version to use:
// Use latest (unversioned) endpoint
let options = VouchOptions(version: .latest)

// Use specific version
let options = VouchOptions(version: .version(1))  // Uses /v1/

Core Methods

validate()

Validate an email address with automatic device fingerprinting:
let result = try await vouch.validate("[email protected]")

// Check validation result
if let data = result.data {
    switch data {
    case .validation(let validationData):
        print("Valid email:", result.email ?? "")
        print("Recommendation:", validationData.recommendation.rawValue)
    case .error(let errorData):
        print("Error:", errorData.error, "-", errorData.message)
    }
}
Returns: ValidationResult
email
String?
The normalized email address
error
String?
Error message if request failed
data
ValidationData?
Response data from server (enum with .validation or .error case)
statusCode
Int?
HTTP status code
ValidationData enum cases:
  • .validation(ValidationResponseData) - Successful validation with checks, metadata, and recommendation
  • .error(ErrorResponseData) - API error with error code and message

generateFingerprint()

Get the device fingerprint directly without validating an email:
let fingerprint = try await vouch.generateFingerprint()

print("Device:", fingerprint.hardware.deviceModel)
print("Screen:", "\(fingerprint.hardware.screenWidth)x\(fingerprint.hardware.screenHeight)")
print("OS:", fingerprint.system.osVersion)
print("Fonts:", fingerprint.fonts.fonts.count)
Returns: Fingerprint with the following signals:
hardware
HardwareSignals
Device hardware information (screen, CPU, memory, model)
fonts
FontSignals
System fonts and SHA-256 hash
system
SystemSignals
iOS version, language, locale, timezone
storage
StorageSignals
UserDefaults, Keychain, FileSystem availability
timestamp
Int64
Unix timestamp in milliseconds
version
String
SDK version (e.g., “2.0.0”)

Error Handling

The SDK uses Swift’s native error handling with three levels of error responses:

1. Network/Request Errors (thrown exceptions)

do {
    let result = try await vouch.validate(email)
    // Handle result
} catch VouchError.fingerprintGenerationFailed(let error) {
    print("Fingerprint generation failed:", error)
} catch VouchError.networkError(let error) {
    print("Network error:", error)
} catch {
    print("Unexpected error:", error)
}

2. API Errors (in ValidationData.error case)

if let data = result.data {
    switch data {
    case .error(let errorData):
        // API returned an error (400 status, invalid format, etc.)
        print("Error code:", errorData.error)  // e.g., "invalid_email"
        print("Message:", errorData.message)  // e.g., "Email format is invalid"
    case .validation(let validationData):
        // Handle successful validation
        print("Recommendation:", validationData.recommendation.rawValue)
    }
}

3. Result-level Errors (in result.error)

if let error = result.error {
    print("Top-level error:", error)
    print("Status code:", result.statusCode ?? 0)
}

Common Errors

ErrorDescriptionSolution
fingerprintGenerationFailedDevice fingerprint could not be generatedCheck device capabilities
networkErrorNetwork request failedCheck internet connection
invalidResponseAPI returned unexpected dataVerify API key and project ID

Privacy & Permissions

No Permissions Required

The Vouch iOS SDK does not require any device permissions. It only accesses publicly available system APIs.
Important: No Info.plist usage descriptions are required. The SDK works without requesting any permissions from the user.
No permissions needed for:
  • ❌ Location
  • ❌ Camera/Photos
  • ❌ Contacts
  • ❌ Microphone
  • ❌ Bluetooth

Data Collection

The SDK collects device fingerprint data for fraud prevention. You must disclose this in:
  1. Your app’s privacy policy
  2. App Store privacy nutrition label
  3. GDPR/CCPA notices (if applicable)
What data is collected:
  • Hardware signals: Screen dimensions, CPU cores, memory, device model
  • Font signals: System fonts and SHA-256 hash
  • System signals: iOS version, language, locale, timezone
  • Storage signals: UserDefaults, Keychain, FileSystem availability
No personally identifiable information (PII) is collected. All data is technical device and system information.
The SDK starts fingerprint generation immediately when initialized, so the first validate() call can reuse the cached fingerprint.

Best Practices

1. Initialize Early

Initialize the SDK as early as possible to allow fingerprint generation to complete:
@main
struct YourApp: App {
    let vouch = Vouch(projectId: "...", apiKey: "...")

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(vouch)
        }
    }
}

2. Handle Errors Gracefully

Always handle validation errors without blocking the user:
do {
    let result = try await vouch.validate(email)

    if let data = result.data {
        switch data {
        case .validation(let validationData):
            if validationData.recommendation == .allow {
                // Proceed
            } else {
                // Show user-friendly warning
                showWarning("Please verify your email")
            }
        case .error(let errorData):
            showError(errorData.message)
        }
    }
} catch {
    // Don't block the user, allow them to proceed or retry
    showError("Validation unavailable, please try again")
}

3. Use Environment Objects (SwiftUI)

Share the Vouch instance across your app using environment objects:
ContentView()
    .environmentObject(vouch)

// Then in child views:
@EnvironmentObject var vouch: Vouch

API Reference

Vouch

Main SDK class for email validation and fingerprinting.
public class Vouch {
    public init(projectId: String, apiKey: String, options: VouchOptions = VouchOptions())

    public func validate(_ email: String) async throws -> ValidationResult
    public func generateFingerprint() async throws -> Fingerprint
}

VouchOptions

SDK configuration options.
public struct VouchOptions {
    public let endpoint: String  // Default: "https://api.vouch.expert"
    public let version: APIVersion  // Default: .latest
}

APIVersion

API version specification.
public enum APIVersion {
    case latest  // Unversioned endpoint
    case version(Int)  // Versioned endpoint (e.g., /v1/)
}

ValidationResult

Email validation response structure.
public struct ValidationResult {
    public let email: String?  // Normalized email address
    public let error: String?  // Error message if request failed
    public let data: ValidationData?  // Response data from server
    public let statusCode: Int?  // HTTP status code
}

ValidationData

Validation response data - enum with two cases.
public enum ValidationData {
    case validation(ValidationResponseData)  // Successful validation
    case error(ErrorResponseData)  // API returned an error
}

ValidationResponseData

Successful validation response containing checks and recommendation.
public struct ValidationResponseData {
    public let checks: ValidationChecks
    public let metadata: ValidationMetadata
    public let recommendation: Recommendation
    public let signals: [String]
}

public enum Recommendation: String {
    case allow  // Email should be allowed
    case block  // Email should be blocked
    case flag   // Email should be flagged for review
}

ValidationChecks

Results of individual validation checks.
public struct ValidationChecks {
    public let syntax: CheckResult?  // Syntax validation
    public let alias: CheckResult?  // Alias/plus addressing detection
    public let disposable: CheckResult?  // Disposable email detection
    public let ip: CheckResult?  // IP address check
    public let mx: CheckResult?  // MX record validation
}

public struct CheckResult {
    public let latency: Int  // Check latency in milliseconds
    public let pass: Bool  // Whether check passed
    public let error: String?  // Error message if check failed
}

ValidationMetadata

Metadata about the validation request.
public struct ValidationMetadata {
    public let fingerprintId: String?  // Device fingerprint ID
    public let previousSignups: Int  // Previous signups from this device
    public let totalLatency: Int  // Total validation latency in ms
}

ErrorResponseData

Error response from the API.
public struct ErrorResponseData {
    public let error: String  // Error code (e.g., "invalid_email")
    public let message: String  // Human-readable error message
}

Support

For issues and questions: