Skip to content

DEA SDK for iOS

Detect whether an app session comes from a real person. The SDK observes touch, stylus, pointer, scroll and device-motion input across every window of your iOS app and scores the session on the device.

On this page: Prerequisites · Install · Initialize the SDK · Ask for consent · Use the classification · Verify your setup · Before you release · Next steps

Prerequisites

  • An appId and appKey for this app — see Accounts and keys
  • iOS 13.0 or later deployment target
  • Xcode with Swift 5.9 or later
  • For Attest with App Attest: iOS 14.0 or later on the device
  • For the SwiftUI consent alert: iOS 15.0 or later (UIKit apps can ask from iOS 13)

The SDK builds for iOS devices (arm64) and the iOS Simulator (arm64 and x86_64).

Install

Not available yet. DEA does not publish the iOS SDK for release 0.19.0. There is no Swift package URL and no CocoaPods podspec to install it from: the podspec URLs on sdk.dep-protocol.com answer 404, and the release manifest lists no iOS package.

The SDK is two modules: DeaSDK, the SDK itself, and DeaBase, which DeaSDK depends on at the exact same version and which defines a few shared types (Consent, SessionInfo, JSONValue, CaptureEvent, KeyClass). Your app target links DeaSDK, and DeaBase comes with it. Import DeaBase as well in files that use those types.

Platform setup

No setup is needed for capture. The SDK needs no permission prompt and no Info.plist entry: it reads touches through passive gesture recognizers and motion through Core Motion's device motion, neither of which asks the user. It does not change your app delegate or swizzle anything.

Attest has additional setup (the App Attest capability).

Privacy manifest

Warning: The SDK does not include a privacy manifest (PrivacyInfo.xcprivacy). Your app's own privacy manifest has to cover what the SDK does.

The SDK calls these APIs, which Apple lists as required-reason APIs:

API Apple's category Why the SDK calls it
UserDefaults NSPrivacyAccessedAPICategoryUserDefaults Keeps the user's consent answer
ProcessInfo.systemUptime NSPrivacyAccessedAPICategorySystemBootTime Timestamps scroll and hover input on the same clock as touches
stat, lstat NSPrivacyAccessedAPICategoryFileTimestamp During Attest, checks for files a jailbreak leaves behind

What the SDK records, stores and sends is described on Privacy and network.

Initialize the SDK

Create one DeaSdk for the whole app and call start() as soon as the app launches, so that capture covers the whole session. Keep the instance somewhere every screen can reach it:

import DeaSDK

enum Dea {
    /// The app's one DEA SDK instance. Nil until `start()` has run.
    private(set) static var sdk: DeaSdk?

    static func start() {
        guard sdk == nil else { return }
        let dea = DeaSdk(
            options: DeaSdkOptions(appId: "YOUR_APP_ID", appKey: "YOUR_APP_KEY")
        )
        dea.start()
        sdk = dea
    }
}

appId and appKey are the pair DEA issued for this iOS app (see Accounts and keys). Reading the classification makes no request to DEA and does not use appKey; Attest needs it, and without it answers .unsupported without contacting DEA. See Options.

The examples in this guide use this Dea.sdk holder.

Note: The samples in this guide are written for the Swift 5 language mode. In the Swift 6 language mode, declare the holder's property as nonisolated(unsafe) private(set) static var sdk: DeaSdk?.

SwiftUI

import SwiftUI

@main
struct MyApp: App {
    init() {
        Dea.start()
    }

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

UIKit

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        Dea.start()
        return true
    }
}
  • Call start() once, on the main thread, at launch. Windows created later — alerts, a second scene on iPad — are picked up when they appear.
  • Create one instance. There is no shared singleton: each DeaSdk you create captures on its own, so keep the one you made.
  • The initializer never throws. If the scoring engine cannot load, the SDK still starts, ready is false, and coreFault says why.
  • Capture is global: every touch in every window, trackpad and mouse scrolling, pointer hover, the motion sensors, and app lifecycle. There is no view to wrap and nothing to register.
  • When it is created, the SDK checks DEA's server for a newer scoring model, in the background. Nothing waits for that check. See Options.
  • Nothing is recorded until the user consents. See the next step.

By default the SDK records nothing until the user agrees. This step is required: an app that never asks collects no data and never gets a classification.

The SDK ships a ready-made alert. In SwiftUI (iOS 15 or later), attach it to your first screen:

import DeaBase
import DeaSDK
import SwiftUI

struct ContentView: View {
    @State private var askingConsent = false

    var body: some View {
        let home = Text("Home")
            .onAppear {
                if let dea = Dea.sdk, !dea.consent.isAnswered {
                    askingConsent = true
                }
            }

        if let dea = Dea.sdk {
            home.deaConsent(dea.consent, asking: $askingConsent) { agreed in
                print("DEA consent: \(agreed)")
            }
        } else {
            home
        }
    }
}

In UIKit, ask from your first view controller:

import DeaSDK
import UIKit

class HomeViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        Dea.sdk?.requestConsent(from: self) { agreed in
            print("DEA consent: \(agreed)")
        }
    }
}

The answer is stored on the device. To use your own UI, change the wording, or ask again, see User Consent.

Use the classification

Read the classification at the moment you make a decision.

import DeaSDK

func onSignUp(_ form: SignUpForm) {
    let result = (try? Dea.sdk?.getClassification())?.result ?? .insufficient

    switch result {
    case .human:
        submitSignUp(form)
    case .bot, .undetermined, .insufficient:
        showChallenge() // your fallback: a captcha, a delay, a manual review
    }
}

Replace SignUpForm, submitSignUp and showChallenge with your own.

getClassification() first waits for input the app has already received to be scored — usually nothing — then reads the score and classifies it against the loaded model's thresholds. It does not call the network. .insufficient means the session is not sufficient yet — it has fewer actions than the model asks for, 8 with the built-in model (see Evidence) — and value is nil. .undetermined means the score falls between the model's two thresholds. Decide what your app does in each case, rather than treating it as .human or .bot. How the score and the thresholds work is explained in How scoring works.

Verify your setup

The production SDK writes no log. Check it from code instead:

  1. Run the app on a device or the simulator, accept the consent alert, and use the app — tap, scroll, swipe — for at least 8 actions, the number the built-in model needs.

  2. Print the SDK's state, for example from a debug button:

import DeaSDK

if let dea = Dea.sdk {
    print("ready:", dea.ready)                       // true
    print("fault:", dea.coreFault ?? "none")         // none
    print("consent:", dea.consent.decision)          // granted
    print("session:", dea.sessionInfo.session)       // a UUID
    print("sdk:", dea.sessionInfo.sdk.version)       // the SDK version
    print("model:", dea.modelInfo)                   // the model scoring now
    print("motion Hz:", dea.sessionInfo.device.sensorRate) // 50 on a device
    print("processed:", dea.coreCounts.samples)      // grows as you touch
    print("score:", String(describing: try? dea.getClassification()))
}
  1. processed should grow while you touch the screen, and getClassification().result leaves .insufficient once actions reaches 8. If ready is false or nothing grows, see Troubleshooting.

sensorRate is 0 on the simulator, which has no motion sensors.

Before you release

  • [ ] appKey is set, if you use Attest — see Options
  • [ ] The consent alert is shown, or your own consent flow calls setConsent — see User Consent
  • [ ] The classification is read at the decision point, with a fallback for .bot, .undetermined and .insufficient — see Human Scoring
  • [ ] Capture is paused on password and payment screens — see Sensitive Screens
  • [ ] reset() is called on logout — see Sessions
  • [ ] The App Attest capability is added, if you use Attest — see Attest
  • [ ] attest() and renewAttestation() are never called on the main thread
  • [ ] Your app's privacy manifest covers the SDK — see Privacy manifest

See also Going to production.

Next steps