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.3.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-IN/vouch-sdk-ios", from: "0.3.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 {
    let result = await vouch.validate("[email protected]")

    if result.isAllowed {
        print("Valid:", result.email ?? "")
    } else {
        print("Error:", result.errorMessage ?? "Validation failed")
    }
}

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

            await MainActor.run {
                isValidating = false

                if result.isAllowed {
                    isValid = true
                    validationMessage = "Email validated"
                } else {
                    isValid = false
                    validationMessage = result.errorMessage ?? "Email validation failed"
                }
            }
        }
    }
}

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 {
                    let result = await vouch.validate(emailToValidate)

                    await MainActor.run {
                        if result.isAllowed {
                            validationState = .valid
                        } else {
                            validationState = .invalid(result.errorMessage ?? "Invalid email")
                        }
                    }
                }
            }
    }
}

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

            await MainActor.run {
                signUpButton.isEnabled = true

                if result.isAllowed {
                    resultLabel.text = "Valid email"
                    resultLabel.textColor = .systemGreen
                    // Proceed with sign-up
                } else {
                    resultLabel.text = result.errorMessage ?? "Invalid email"
                    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. Never throws — all errors are returned in the result.
let result = await vouch.validate("[email protected]")

if result.isAllowed {
    print("Valid email:", result.email ?? "")
} else {
    print("Error:", result.errorMessage ?? "Validation failed")
}
For more detail, access the full response:
let result = await vouch.validate("[email protected]")

// Access recommendation directly
print("Recommendation:", result.recommendation ?? "none")
print("Signals:", result.signals ?? [])

// Or unwrap the full response data
if let data = result.data {
    switch data {
    case .validation(let response):
        print("Checks:", response.checks)
        print("Metadata:", response.metadata)
    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
Convenience properties:
isAllowed
Bool
Whether the validation passed (recommendation is “allow”)
recommendation
String?
The recommendation string (“allow”, “block”, or “flag”)
signals
[String]?
The validation signals array
errorMessage
String?
A descriptive error message for any failure scenario, nil if allowed

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 never throws from validate(). All errors (network failures, invalid format, API errors) are captured in the ValidationResult:
let result = await vouch.validate(email)

if result.isAllowed {
    // Proceed with sign-up
} else {
    // result.errorMessage contains a descriptive error for any failure:
    // - "Invalid email format" (local validation)
    // - "Network error: ..." (connectivity issues)
    // - "Fingerprint generation failed: ..." (device signal error)
    // - "Email blocked: block" (API rejected the email)
    // - API error messages
    showError(result.errorMessage ?? "Validation failed")
}

Accessing Detailed Error Info

For cases where you need more control:
let result = await vouch.validate(email)

if let data = result.data {
    switch data {
    case .validation(let response):
        if response.recommendation == "allow" {
            // Proceed
        } else if response.recommendation == "flag" {
            // Show warning but allow
            showWarning("Please verify your email")
        } else {
            // Blocked
            showError("Email not accepted")
        }
    case .error(let errorData):
        // API returned a structured error
        print("Error code:", errorData.error)   // e.g., "invalid_email"
        print("Message:", errorData.message)    // e.g., "Email format is invalid"
    }
} else if let error = result.error {
    // Network or fingerprint error (no API response)
    print("Error:", error)
    print("Status code:", result.statusCode ?? 0)
}

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

if result.isAllowed {
    // Proceed with sign-up
} else if result.recommendation == "flag" {
    // Show user-friendly warning but allow
    showWarning("Please verify your email")
} else {
    // Don't block the user entirely, allow retry
    showError(result.errorMessage ?? "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 actor Vouch {
    public init(projectId: String, apiKey: String, options: VouchOptions = VouchOptions())

    public func validate(_ email: String) async -> 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/)
}

ValidationAction

Validation action enum used in toggle configuration.
public enum ValidationAction: String, Codable {
    case allow
    case block
    case flag
}

ValidationResult

Email validation response structure with convenience properties.
public struct ValidationResult {
    public let email: String?
    public let error: String?
    public let data: ValidationData?
    public let statusCode: Int?

    // Convenience properties
    public var isAllowed: Bool
    public var recommendation: String?
    public var signals: [String]?
    public var errorMessage: String?
}

ValidationData

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

ValidationResponse

Successful validation response containing checks and recommendation.
public struct ValidationResponse {
    public let checks: [String: CheckResult]
    public let message: String?
    public let metadata: ValidationMetadata
    public let recommendation: String  // "allow", "block", or "flag"
    public let signals: [String]
}

CheckResult

Individual check result.
public struct CheckResult {
    public let error: String?
    public let latency: Int
    public let metadata: [String: JSONValue]?
    public let pass: Bool
}

ValidationMetadata

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

DeviceData

Device fingerprint data returned in validation results.
public struct DeviceData {
    public let emailsUsed: Int
    public let firstSeen: Int
    public let isKnownDevice: Bool
    public let isNewEmail: Bool
    public let lastSeen: Int?
    public let previousSignups: Int
}

IPData

IP address analysis data.
public struct IPData {
    public let ip: String
    public let isAnonymous: Bool  // True if VPN, Tor, or datacenter IP detected
    public let isFraud: Bool
}

ValidationToggles

Toggle configuration for which validations to run.
public struct ValidationToggles {
    public let alias: ValidationAction?
    public let catchall: ValidationAction?
    public let device: ValidationAction?
    public let disposable: ValidationAction?
    public let ip: ValidationAction?
    public let mx: ValidationAction?
    public let roleEmail: ValidationAction?
    public let smtp: ValidationAction?
    public let syntax: ValidationAction?
}

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: