Skip to main content
The iOS SDK mirrors the browser SDK: configure it on startup, call getSession() when the user performs a sensitive action, and POST the sealed token to your backend for verification.
import Tripwire

try TripwireClient.shared.configure(
    TripwireConfiguration(publishableKey: "pk_live_...")
)

// At action time (signup, login, checkout)
let handoff = try await TripwireClient.shared.getSession()
The mobile SDKs are currently in private beta. Contact support for access credentials and distribution instructions.

Requirements

  • iOS 14+, macOS 11+, tvOS 14+, or Mac Catalyst 14+
  • Swift 5.9+
  • Xcode 15+

Install

Add Tripwire as a Swift Package Manager dependency. While the SDK is in private beta, add it by local path:
// Package.swift
dependencies: [
    .package(path: "../tripwire/sdks/ios"),
],
Once the public mirror ships you’ll point at https://github.com/abxy-labs/tripwire-ios and pin a release tag.

Configure on startup

Configure once at app launch — typically from your App initializer — so signal collection is running by the time the first screen appears.
import SwiftUI
import Tripwire

@main
struct MyApp: App {
    init() {
        do {
            try TripwireClient.shared.configure(
                TripwireConfiguration(publishableKey: "pk_live_your_publishable_key")
            )
        } catch {
            // Reporting only — do not crash on Tripwire misconfiguration.
            print("Tripwire failed to configure:", error)
        }
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

TripwireConfiguration

public struct TripwireConfiguration: Sendable {
    public let publishableKey: String
    public var enableBehavioralSignals: Bool
    public var enableAntiTamper: Bool
    public var enableHiddenWebView: Bool
    public var enableCloudIdentifier: Bool
}
FieldDefaultDescription
publishableKeyRequired. Must start with pk_live_ or pk_test_. Secret keys (sk_*) are rejected at configure-time.
enableBehavioralSignalstrueCoreMotion sampling and opt-in touch observers.
enableAntiTampertrueJailbreak, debugger, simulator, and hook-detection signals.
enableHiddenWebViewtrueEnables the attach(to:) handoff so an in-app WKWebView shares this session.
enableCloudIdentifierfalseOpt-in iCloud KVS continuity hint. Requires the iCloud Key-Value Store entitlement on your app to survive reinstall.

Get a session at action time

Call getSession() from an async context right before the sensitive action — not on app launch.
struct CheckoutView: View {
    func submit() async {
        do {
            let handoff = try await TripwireClient.shared.getSession()

            var request = URLRequest(url: URL(string: "https://api.example.com/checkout")!)
            request.httpMethod = "POST"
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            request.httpBody = try JSONEncoder().encode(CheckoutRequest(
                items: cart,
                tripwire: .init(
                    sessionId: handoff.sessionId,
                    sealedToken: handoff.sealedToken
                )
            ))

            _ = try await URLSession.shared.data(for: request)
        } catch {
            // Degrade gracefully — see Error handling below.
        }
    }
}
SessionHandoff is a small value type:
public struct SessionHandoff: Sendable, Equatable {
    public let sessionId: String
    public let sealedToken: String
}
  • sessionId is stable for the life of the client.
  • sealedToken is fresh on every call. Send the most recent one to your backend.
  • The SDK never surfaces verdicts, scores, or visitor IDs to the device — verify on your server.

Behavioral capture

Touch-stroke dynamics are opt-in per screen. Call observeTouches(on:) once the view is mounted.
class CheckoutViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        TripwireClient.shared.observeTouches(on: view, contextId: "checkout")
    }
}
The observer attaches a non-consuming gesture recognizer, so your existing gestures keep working unchanged. In SwiftUI, reach for a UIViewRepresentable wrapper to expose the underlying UIView.

WebView correlation

If your app hosts Tripwire-protected web content in a WKWebView, attach it so the web SDK reuses the native session.
import WebKit

let webView = WKWebView(frame: .zero)
TripwireClient.shared.attach(to: webView)
webView.load(URLRequest(url: URL(string: "https://your-app.example.com/signup")!))
The native bridge exposes the session handoff as window.__TRIPWIRE_NATIVE__, which the browser SDK picks up when loaded inside the WebView.

API reference

MethodDescription
TripwireClient.shared.configure(_:)Validates the publishable key and starts the runtime. Throws TripwireError on invalid input.
TripwireClient.shared.getSession() async throws -> SessionHandoffFlushes pending observations and returns a sealed handoff. Every call produces a fresh sealedToken.
TripwireClient.shared.waitForFingerprint() async throwsResolves when the durable visitor fingerprint is ready. Optional — getSession() works without it.
TripwireClient.shared.observeTouches(on:contextId:)Attaches a non-consuming gesture recognizer to the view. Call once per screen.
TripwireClient.shared.attach(to:)Shares the current session with a hosted WKWebView.
TripwireClient.shared.destroy()Stops timers and releases resources. Rarely needed outside tests.

Error handling

TripwireError is a Sendable struct carrying a stable code, a human-readable message, an optional underlying error, and a retryable flag.
public struct TripwireError: Error, Sendable {
    public enum Code: String, Sendable {
        case invalidPublishableKey = "config.invalid_publishable_key"
        case notConfigured         = "client.not_configured"
        case sessionCreateFailed   = "session.create_failed"
        case batchPostFailed       = "transport.batch_post_failed"
        case handshakeExpired      = "session.handshake_expired"
        case rateLimited           = "transport.rate_limited"
        case serverUpgradeRequired = "transport.upgrade_required"
        case network               = "transport.network"
        case cryptoFailure         = "crypto.failure"
        case internalError         = "internal"
    }

    public let code: Code
    public let message: String
    public let underlying: Error?
    public let retryable: Bool
}
CodeRetryableWhen it happens
config.invalid_publishable_keynopublishableKey is missing, uses the sk_* secret prefix, or doesn’t match pk_live_* / pk_test_*.
client.not_configurednoA client method was called before configure(_:).
session.create_failedvariesSession handshake with the Tripwire API failed.
session.handshake_expirednoThe server expired the session. Call configure(_:) again to mint a new one.
transport.batch_post_failedvariesA signal batch upload failed.
transport.rate_limitedyesHTTP 429 from the API. Back off and retry.
transport.upgrade_requirednoHTTP 410/426. The SDK version is too old — upgrade the app.
transport.networkyesDNS, TLS, or connectivity failure.
crypto.failurenoA crypto primitive failed. Usually a platform issue worth reporting.
internalnoUnexpected state. File a support ticket with the message and underlying error.
The code values are stable. Category prefixes (config.*, transport.*) are safe to branch on with a wildcard — new codes within a category keep the same retry semantics.

Fallback policy

If Tripwire fails, your app should still work. Log the error and continue without a handoff.
func tripwireHandoff() async -> SessionHandoff? {
    do {
        return try await TripwireClient.shared.getSession()
    } catch let err as TripwireError where err.retryable {
        return try? await TripwireClient.shared.getSession()
    } catch {
        reportError(error)
        return nil
    }
}
See Going to production for guidance on fall-open vs. fall-closed policy.

Best practices

  • Configure once at launch so signals are collecting before any screen opens.
  • Call getSession() late — right before the sensitive action, from an async context.
  • Degrade gracefully — never block the user if the SDK fails to produce a handoff.
  • Reuse the shared clientTripwireClient.shared is a process-wide singleton.

What’s next

Server verification

Verify the sealed token on your backend

Browser SDK

Equivalent guide for the web surface
https://mintcdn.com/abxy/GMpwX6jvN2QwWvP1/images/android.svg?fit=max&auto=format&n=GMpwX6jvN2QwWvP1&q=85&s=83f0b17dba056171a7bfb26b123edaea

Android SDK

Native Android integration

Going to production

Rollout checklist and monitoring