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-platform/vouch-sdk
  4. Select version requirements (2.0.0+)
  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-platform/vouch-sdk", from: "2.0.0")
]
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]")

        if result.success && result.valid == true {
            print("✓ Email is valid")
        } else {
            print("✗ Invalid email: \(result.error ?? "Unknown error")")
        }
    } catch {
        print("Validation 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 result.success && result.valid == true {
                        isValid = true
                        validationMessage = "✓ Valid email"
                        // Proceed with sign-up
                    } 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 result.success && result.valid == true {
                                validationState = .valid
                            } 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 result.success && result.valid == true {
                        resultLabel.text = "✓ Valid email"
                        resultLabel.textColor = .systemGreen
                        // Proceed with sign-up
                    } 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
    fingerprintOptions: FingerprintOptions(
        enableFonts: true  // Enable font fingerprinting (slower but more unique)
    )
)

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 result.success && result.valid == true {
    print("Valid email:", result.email ?? "")
} else {
    print("Invalid:", result.error ?? "Unknown error")
}
Returns: ValidationResult
success
Bool
Whether the API call was successful
valid
Bool?
Whether the email is valid (nil if success is false)
email
String?
The normalized email address
error
String?
Error message if validation failed
data
AnyCodable?
Complete server response data
statusCode
Int?
HTTP status code

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:
do {
    let result = try await vouch.validate(email)

    if result.success && result.valid == true {
        // Email is valid
    } else {
        // Email is invalid
        print("Error:", result.error ?? "Unknown error")
    }
} catch VouchError.fingerprintGenerationFailed(let error) {
    print("Fingerprint generation failed:", error)
} catch VouchError.networkError(let error) {
    print("Network error:", error)
} catch {
    print("Unexpected error:", error)
}

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.

Performance

  • Fingerprint Generation: ~100-500ms (first time)
  • Email Validation: Instant local validation + network request time
  • Font Collection: ~200-1000ms (can be disabled)
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 result.success && result.valid == true {
        // Proceed
    } else {
        // Show user-friendly error
        showError(result.error ?? "Please check your email")
    }
} 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
    public let fingerprintOptions: FingerprintOptions
}

APIVersion

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

FingerprintOptions

Fingerprint collection configuration.
public struct FingerprintOptions {
    public let enableFonts: Bool  // Default: true (~200-1000ms)
}

Support

For issues and questions: