> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pesapay.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS

> Install the zkTLS SDK and run a proving session in a UIKit or SwiftUI app.

## Requirements

iOS 14.0 or later. Built and tested with Xcode 26.3; Swift tools 5.9. The package brings its own privacy manifest and needs no `Info.plist` key or entitlement to run.

## Install

<CodeGroup>
  ```swift Package.swift theme={null}
  .package(url: "https://github.com/Cr3dentials/zktls-sdk-releases", from: "1.0.0"),
  // in your target's dependencies:
  .product(name: "ZktlsSDK", package: "zktls-sdk-releases"),
  ```

  ```ruby Podfile theme={null}
  source 'https://github.com/Cr3dentials/zktls-sdk-releases.git'
  source 'https://cdn.cocoapods.org/'
  platform :ios, '14.0'

  target 'YourApp' do
    pod 'ZktlsSDK', '~> 1.0'
  end
  ```
</CodeGroup>

In Xcode you can also use File, Add Package Dependencies, with the same URL. Keep the releases source first in a `Podfile`: CocoaPods takes a pod from the first source that has it.

## Build the configuration

One `SessionConfig` per session the user starts. Field names are the same in Swift and Kotlin.

| Field | What it holds | Rules |
| - | - | - |
| `baseUrl` | The front door URL | `https://` only |
| `appCredential` | Your app's credential | Never log it |
| `userIdHash` | Your own hash of your user ID | Never the raw ID. Same value for the same user. At most 128 characters |
| `profileId`, `policyId` | What the session proves | The policy must be in the policy bundle for that profile |
| `idempotencyKey` | This session's key | New every session (a UUID works). 1 to 117 bytes |
| `clientRegion` | The user's country, ISO 3166-1 alpha-2 | `""` if unknown: the SDK times the offered cells and uses the fastest |
| `networkKey` | A label for the current network | Any stable string. Never leaves the device |
| `trustBundleJson` | The trust bundle, as fetched | Refetch before it is 30 minutes old |
| `trustOperatorKey` | The operator public key | PEM or base64 DER. Pinned in your app |
| `policyBundleJson` | The signed policy bundle | |
| `policyBundleDigest` | The digest that pins the policy bundle | Pinned in your app |

```swift theme={null}
let config = SessionConfig(
    baseUrl: frontDoor, appCredential: credential, userIdHash: hash(of: userId),
    profileId: profileId, policyId: policyId,
    idempotencyKey: UUID().uuidString,
    clientRegion: country ?? "", networkKey: "default",
    trustBundleJson: trustBundle, trustOperatorKey: operatorKey,
    policyBundleJson: policyBundle, policyBundleDigest: policyBundleDigest)
```

## Run a session

```swift theme={null}
import UIKit
import ZktlsSDK

final class ProveViewController: UIViewController {
    private var client: ZktlsClient?

    func prove(config: SessionConfig) async {
        let client = ZktlsClient(config: config)             // one client per session
        self.client = client
        await client.onProgress { phase, percent in
            Task { @MainActor in /* update your progress view */ }
        }
        do {
            try await client.login(host: WebViewLoginPresenter(presentingFrom: self))
            let lifecycle = ZkTlsLifecycle(host: UIApplication.shared, handle: client.lifecycleHandle)
            lifecycle.onExpiry = { client.cancel() }        // iOS ended our background time
            lifecycle.begin()
            defer { lifecycle.end() }                        // every path, including failure
            try await client.capture()
            let report = try await client.prove()
            guard report.accepted else { return show("Not proven: \(report.verdict)") }
            let claim = try await client.claim()           // verified against the trust bundle
            await send(claim.encoded)                      // your backend verifies it again
        } catch let e as SdkError {
            show(message(for: e))                          // never retry automatically
        } catch {
            show("Something went wrong. Please try again.")
        }
    }

    func cancelTapped() { client?.cancel() }
}
```

`show`, `send` and `message(for:)` are yours.

## Things to know

* **Begin the lifecycle after `login`.** The login page is the user's to take time over, for example to fetch an SMS code. `ZkTlsLifecycle` asks iOS for background time while the capture runs, usually about 30 seconds.
* **Network changes.** The lifecycle notices a Wi-Fi to cellular handover and reconnects.
* **The login is private.** Each login runs on a new non-persistent `WKWebsiteDataStore` and touches none of your app's cookies.
* **The disclosure.** `WebViewLoginPresenter` shows it before the page, every time. Pass `disclosure:` to translate it. A custom presenter must show an equivalent one.
* **Threads.** Every verb runs on the SDK's own threads. `onProgress` calls you on an SDK thread, so hop to the main actor for UI.
* **Logging.** Log error variants and their text only. `print(config)` shows every field, the app credential included.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.