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
Swift Package Manager (Recommended)
Via Xcode
- Open your project in Xcode
- Go to File → Add Package Dependencies
- Enter the repository URL:
https://github.com/Vouch-IN/vouch-sdk-ios
- Select version requirements: “Up to Next Major Version” →
0.3.0
- Add
VouchSDK to your target
Via Package.swift
Add the package dependency to your 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
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
The normalized email address
Error message if request failed
Response data from server (enum with .validation or .error case)
Convenience properties:
Whether the validation passed (recommendation is “allow”)
The recommendation string (“allow”, “block”, or “flag”)
The validation signals array
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:
Device hardware information (screen, CPU, memory, model)
System fonts and SHA-256 hash
iOS version, language, locale, timezone
UserDefaults, Keychain, FileSystem availability
Unix timestamp in milliseconds
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:
- Your app’s privacy policy
- App Store privacy nutrition label
- 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.
- 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
}
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: