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.1.7
- 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.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
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
The normalized email address
Error message if request failed
Response data from server (enum with .validation or .error case)
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:
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 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
| Error | Description | Solution |
|---|
fingerprintGenerationFailed | Device fingerprint could not be generated | Check device capabilities |
networkError | Network request failed | Check internet connection |
invalidResponse | API returned unexpected data | Verify 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:
- 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.
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
}
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: