iOS SDK (Swift)
Complete setup guide for the VerifyStack iOS SDK — Swift Package Manager installation, initialization, signal collection, and API reference.
The VerifyStack iOS SDK is a native Swift library that collects device intelligence, behavioral biometrics, sensor fingerprints, and integrity signals on iOS devices. It works with iOS 14+ and is distributed via Swift Package Manager.
Requirements
| Requirement | Minimum |
|---|---|
| iOS | 14.0+ |
| macOS (Mac Catalyst) | 12.0+ |
| Swift | 5.9+ |
| Xcode | 15.0+ |
Installation
Swift Package Manager (Recommended)
In Xcode, go to File → Add Package Dependencies and enter the repository URL:
https://github.com/verifystack/verifystack-ios.gitOr add it directly in your Package.swift:
dependencies: [
.package(url: "https://github.com/verifystack/verifystack-ios.git", from: "1.0.0")
]CocoaPods
pod 'VerifyStack', '~> 1.0'Quick Start (3 Steps)
Step 1: Import & Initialize
import VerifyStack
// Initialize with your API key and endpoint
let verifyStack = VerifyStack(
apiKey: "pk_live_xxxxxxxxx",
endpoint: "https://verifystack.io"
)Step 2: Start Tracking (Optional)
// Call early in your app lifecycle for best accuracy
verifyStack.startTracking()Step 3: Make a Decision Request
do {
let decision = try await verifyStack.decide(
userId: "user_123",
action: .login
)
switch decision.decision {
case "allow":
// Proceed with login
print("Allowed — score: \(decision.score)")
case "challenge":
// Show 2FA or CAPTCHA
presentChallenge(requestId: decision.requestId)
case "deny":
// Block the attempt
showBlockedMessage(reasons: decision.reasons)
default:
break
}
} catch {
// Network error — fail open or closed based on your policy
print("VerifyStack error: \(error.localizedDescription)")
}API Key — Which Key to Use
| Key Type | Prefix | Use in iOS SDK? |
|---|---|---|
| Publishable | pk_live_xxxxxxxxx | ✅ Yes — always use this in the SDK |
| Secret | sk_live_xxxxxxxxx | ❌ Never — use only on your backend server |
Configuration Options
let config = VSConfiguration(
apiKey: "pk_live_xxxxxxxxx", // ← Always use your PUBLISHABLE key (pk_)
endpoint: "https://verifystack.io",
timeoutSeconds: 10, // Request timeout (default: 10)
maxRetries: 3, // Retry on transient failures (default: 3)
enableSensors: true, // Gyroscope/accelerometer fingerprinting
enableBehavior: true, // Touch and keystroke biometrics
enableIntegrity: true, // Jailbreak/hooking/debugger detection
enableAdvanced: true, // GPU fingerprint, deep identity
cacheTTLSeconds: 30, // Signal cache duration (default: 30)
debugLogging: false // Console logging (default: false)
)
let verifyStack = VerifyStack(configuration: config)All API Methods
| Method | Description |
|---|---|
| verifyStack.startTracking() | Begin passive behavior + sensor data collection |
| verifyStack.stopTracking() | Stop all passive tracking |
| await verifyStack.collect() | Collect all signals (returns VSCollectionResult) |
| try await verifyStack.decide(userId:action:metadata:) | Collect signals + get fraud decision |
| try await verifyStack.analyze(userId:action:) | Lightweight analysis (no auth required) |
| try await verifyStack.feedback(requestId:wasActuallyFraud:notes:) | Submit ground truth feedback |
| verifyStack.destroy() | Release all resources and stop tracking |
Signal Collection (7 Phases)
The SDK collects signals across 7 specialized phases. Each phase runs independently — if one fails, the others still succeed.
| Phase | Collector | Signals | Spoofing Resistance |
|---|---|---|---|
| 1 | Device | Model, OS, CPU, memory, screen, battery, disk, locale, biometric type | High |
| 2 | Network | WiFi/cellular type, VPN/proxy detection, DNS latency, carrier info | Medium |
| 3 | Visitor ID | Keychain-persisted cross-session identifier (survives app reinstall) | Very High |
| 4 | Behavior | Touch pressure, swipe velocity, keystroke timing, Hurst exponent | Very High |
| 5 | Sensors | Gyroscope bias, accelerometer spectral peaks, magnetometer offset | Extreme |
| 6 | Integrity | Jailbreak, Frida/Substrate hooking, debugger, App Attest | High |
| 7 | Advanced | GPU/Metal timing fingerprint, thermal profile, MEMS composite, Titan ID | Extreme |
Decision Response
let response = try await verifyStack.decide(userId: "user_123", action: .purchase)
response.decision // "allow" | "challenge" | "deny"
response.score // 0–100 (higher = riskier)
response.riskLevel // "low" | "medium" | "high" | "critical"
response.requestId // Unique request ID for feedback
response.reasons // ["new_device", "velocity_anomaly", ...]
response.processingTimeMs // Server-side latency
// Convenience helpers
response.isAllowed // true if decision == "allow"
response.isDenied // true if decision == "deny"
response.isChallenged // true if decision == "challenge"Supported Actions
VSAction.login // User login
VSAction.signup // New account registration
VSAction.purchase // Payment / checkout
VSAction.transfer // Money transfer
VSAction.withdrawal // Funds withdrawal
VSAction.passwordReset // Password reset request
VSAction.accountUpdate // Profile changes
VSAction.cardAdd // Adding payment method
VSAction.pageView // Page/screen view
VSAction.custom // Custom actionError Handling
do {
let result = try await verifyStack.decide(userId: "user_123", action: .login)
} catch let error as VSError {
switch error {
case .networkError(let message):
print("Network error: \(message)")
case .serverError(let statusCode, let message):
print("Server error \(statusCode): \(message)")
case .authError(let message):
print("Auth error: \(message)") // Invalid API key
case .timeout:
print("Request timed out")
case .invalidResponse:
print("Invalid server response")
case .rateLimited(let retryAfter):
print("Rate limited — retry after \(retryAfter ?? 0)s")
case .sdkNotInitialized:
print("SDK not initialized")
}
}Feedback Loop (Improve Accuracy)
Submit ground truth outcomes to train the scoring engine and improve future decisions. This is the single most impactful thing you can do to improve accuracy.
// After confirming fraud or legitimate activity
try await verifyStack.feedback(
requestId: decision.requestId,
wasActuallyFraud: true,
notes: "Confirmed chargeback"
)SwiftUI Integration
import SwiftUI
import VerifyStack
struct LoginView: View {
@State private var isLoading = false
@State private var result: String = ""
// Initialize once at the app level
let vs = VerifyStack(
apiKey: "pk_live_xxxxxxxxx",
endpoint: "https://verifystack.io"
)
var body: some View {
VStack(spacing: 16) {
Button("Sign In") {
Task { await handleLogin() }
}
.disabled(isLoading)
if !result.isEmpty {
Text(result)
.font(.caption)
.foregroundColor(.secondary)
}
}
.onAppear { vs.startTracking() }
.onDisappear { vs.stopTracking() }
}
func handleLogin() async {
isLoading = true
defer { isLoading = false }
do {
let decision = try await vs.decide(
userId: "user_123",
action: .login
)
result = "Decision: \(decision.decision) (score: \(decision.score))"
} catch {
result = "Error: \(error.localizedDescription)"
}
}
}UIKit Integration
import UIKit
import VerifyStack
class LoginViewController: UIViewController {
private let vs = VerifyStack(
apiKey: "pk_live_xxxxxxxxx",
endpoint: "https://verifystack.io"
)
override func viewDidLoad() {
super.viewDidLoad()
vs.startTracking() // Begin passive collection
}
@IBAction func loginTapped(_ sender: UIButton) {
Task {
do {
let decision = try await vs.decide(
userId: "user_123",
action: .login
)
handleDecision(decision)
} catch {
handleError(error)
}
}
}
private func handleDecision(_ d: VSDecideResponse) {
if d.isAllowed {
navigateToHome()
} else if d.isChallenged {
presentMFA(requestId: d.requestId)
} else {
showAlert(title: "Blocked", message: "Suspicious activity detected.")
}
}
deinit { vs.destroy() }
}Privacy & Permissions
The SDK requires no special permissions by default. Optional permissions enhance signal quality:
| Permission | Purpose | Required? |
|---|---|---|
| None | Device, network, integrity, and visitor ID signals | Automatic |
| Motion & Fitness (CoreMotion) | Gyroscope/accelerometer sensor fingerprinting | Optional — prompts user if enableSensors: true |
| Local Network | Enhanced network topology detection | Optional |
Privacy Manifest (PrivacyInfo.xcprivacy)
<!-- Required API declarations for App Store submission -->
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array><string>CA92.1</string></array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array><string>E174.1</string></array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array><string>35F9.1</string></array>
</dict>
</array>Thread Safety
The SDK is fully thread-safe. All public methods can be called from any thread or queue. Internal state is protected with NSLock. Signal collection uses Swift async/await with parallel execution via async let.
Troubleshooting
| Problem | Solution |
|---|---|
| Import error: No such module 'VerifyStack' | Ensure the package is added via File → Add Package Dependencies. Clean build folder (Cmd+Shift+K). |
| Sensor signals are nil | Check that enableSensors is true and the device is not a Simulator (gyroscope/accelerometer unavailable on simulators). |
| Decision returns timeout error | Increase timeoutSeconds in VSConfiguration. Check network connectivity. |
| 401 Unauthorized — API key rejected | The SDK must use a publishable key (pk_live_xxxxxxxxx). If you accidentally used a secret key (sk_live_xxxxxxxxx), replace it with your pk_ key. Find both keys in Dashboard → Settings → API Keys. |
| 403 Forbidden on /feedback | The /feedback endpoint requires a secret key (sk_). You cannot call it from the SDK. Send feedback from your backend server using sk_live_xxxxxxxxx. |
| Rate limited (429) | You're exceeding your plan's request quota. The SDK respects Retry-After headers automatically. |
| Visitor ID changes on every launch | Keychain access may be restricted. Ensure the Keychain Sharing capability is enabled in your Xcode project. |