# zkOBS - Open Banking Services

## Use Case

This guide provides detailed instructions on how to integrate zkMe’s zkTLS SDK into your Flutter mobile application for secure Open Banking Services (zkOBS) verification. Through Platform Channels, your Flutter app can seamlessly interact with the native zkTLS SDK on Android and iOS platforms, enabling user data authorization and verification without redirection to external browsers or third-party applications.

This integration approach aims to reduce user friction and drop-off while ensuring sensitive data is never exposed to the app. Users generate privacy-preserving cryptographic proofs, making it an ideal solution for mobile-first products that require secure verification with a single cross-platform Flutter codebase.

***

## Requirements & Compatibility

Before starting the integration, ensure your development environment meets the following minimum requirements:&#x20;

<table><thead><tr><th width="156.1875">Platform</th><th>Minimum Supported Version</th><th>Target/Distribution</th></tr></thead><tbody><tr><td><strong>Android</strong></td><td>Android 7.0 (API 24)</td><td>Android 14 (API 34)</td></tr><tr><td><strong>iOS</strong></td><td>iOS 13.0</td><td>CocoaPods</td></tr></tbody></table>

The SDK is provided as native libraries. It is compatible with all **stable Flutter releases** that support standard Platform Channels. No additional Flutter-side dependencies are required beyond the services library.

***

## Integration Workflow Overview

Integrating the SDK requires platform-specific native integration for Android and iOS:

* [**Android Native Integration**](#android-native-integration)**:** Configure your Android project to include the SDK and handle calls from Flutter to start the verification process.
* [**iOS Native Integration**](#ios-native-integration)**:** Configure your iOS project to include the SDK and invoke the native zkTLS verification flow from your view controller.

***

## Android Native Integration

This section details how to integrate the zkTLS SDK into the Android portion of your Flutter project and implement the Platform Channel handler to respond to calls from Dart.

### Step 1. Add Maven Repository

Add a private Maven repository in `settings.gradle.kts` (or `settings.gradle`) in the project `root` directory.

{% tabs %}
{% tab title="Kotlin" %}
{% code expandable="true" %}

```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()

        maven {
            url = uri("https://maven.pkg.github.com/zkMeLabs/selvage-verifier-android-sdk")
            credentials {
                username = "GITHUB_OWNER"
                password = "GITHUB_TOKEN"
            }
        }

        // Official Flutter Engine Repository
        maven {
            url = uri("https://storage.googleapis.com/download.flutter.io")
        }
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="Groovy" %}
{% code expandable="true" %}

```groovy
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()

        maven {
            url = uri('https://maven.pkg.github.com/zkMeLabs/selvage-verifier-android-sdk')
            credentials {
                username = 'zktls-selvage'
                password = '<YOUR_GITHUB_TOKEN>'
            }
        }
        
        // Official Flutter Engine Repository
        maven {
            url = uri('https://storage.googleapis.com/download.flutter.io')
        }
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Step 2. Add Dependencies

Add the SDK dependency in the `build.gradle.kts` (or `build.gradle`) file of your `app` module.

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
// build.gradle.kts
dependencies {
    implementation("com.zkme.selvage.verifier:selvage-verifier-sdk:1.0.0")
}
```

{% endtab %}

{% tab title="Groovy" %}

```groovy
// build.gradle
dependencies {
    implementation 'com.zkme.selvage.verifier:selvage-verifier-sdk:1.0.0'
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Note: Click the **Sync Now** button in Android Studio and wait for the dependency download to complete.
{% endhint %}

### Step 3. Handle Flutter Calls and Start Verification

To start the verification process on Android when triggered from Flutter, add the following code to your `MainActivity`. This code invokes the native `SelvageSdk.startVerification()` API and handles verification callbacks, including success, error, and cancellation.

{% tabs %}
{% tab title="Kotlin" %}
{% code expandable="true" %}

```kotlin
import com.zkme.selvage.verifier.SelvageSdk
import com.zkme.selvage.verifier.SelvageVerificationResult
import com.zkme.selvage.verifier.SelvageVerificationError

class MainActivity : AppCompatActivity() {
    
    private fun startVerification() {
        SelvageSdk.startVerification(
            context = this,
            requestId = "your-request-id",
            appId = "your-app-id",          
            apiKey = "your-api-key",       
            providerId = "your-provider-id",
            callback = object : SelvageSdk.Callback {
                override fun onSuccess(result: SelvageVerificationResult) {
                    // Verification successful
                    Log.d("Selvage", "Verification Successful")
                    Log.d("Selvage", "Session ID: ${result.sessionId}")
                    Log.d("Selvage", "Proofs JSON: ${result.proofsJson}")
                    
                    // Send proofsJson to your backend for verification
                }
                
                override fun onError(error: SelvageVerificationError) {
                    // Verification failed
                    Log.e("Selvage", "Verification Failed: ${error.code} - ${error.message}")
                    Toast.makeText(this@MainActivity, 
                        "Verification Failed: ${error.message}", 
                        Toast.LENGTH_SHORT).show()
                }
                
                override fun onCancelled() {
                    // User canceled verification
                    Log.d("Selvage", "User cancels verification")
                    Toast.makeText(this@MainActivity, 
                        "User cancels verification", 
                        Toast.LENGTH_SHORT).show()
                }
            }
        )
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="Java" %}
{% code expandable="true" %}

```java
import com.zkme.selvage.verifier.SelvageSdk;
import com.zkme.selvage.verifier.SelvageVerificationResult;
import com.zkme.selvage.verifier.SelvageVerificationError;

public class MainActivity extends AppCompatActivity {
    
    private void startVerification() {
        SelvageSdk.INSTANCE.startVerification(
            this,
            "your-request-id",
            "your-app-id",
            "your-api-key",
            "your-provider-id",
            new SelvageSdk.Callback() {
                @Overridepublic void onSuccess(@NonNull SelvageVerificationResult result) {
                    Log.d("Selvage", "Verification Successful: " + result.getSessionId());
                    Log.d("Selvage", "Proofs: " + result.getProofsJson());
                }
                
                @Overridepublic void onError(@NonNull SelvageVerificationError error) {
                    Log.e("Selvage", "Verification Failed: " + error.getCode() + " - " + error.getMessage());
                }
                
                @Overridepublic void onCancelled() {
                    Log.d("Selvage", "User cancels verification");
                }
            }
        );
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### API Reference

<details>

<summary><code>SelvageSdk</code></summary>

The main entry class of the SDK, providing verification-related methods.

**Method:** `startVerification`

**Parameters:**

<table><thead><tr><th width="143.90625">Name</th><th width="200.91015625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>context</code></td><td>Context</td><td>Android context, typically an <code>Activity</code>.</td></tr><tr><td><code>requestId</code></td><td>String</td><td>Unique identifier for this verification request.</td></tr><tr><td><code>appId</code></td><td>String</td><td>App ID</td></tr><tr><td><code>apiKey</code></td><td>String</td><td>API key</td></tr><tr><td><code>providerId</code></td><td>String</td><td>Provider identifier for the verification flow.</td></tr><tr><td><code>callback</code></td><td>SelvageSdk.Callback</td><td>Callback used to receive verification results.</td></tr></tbody></table>

**Return Value:** None.

</details>

<details>

<summary><code>SelvageSdk.Callback</code></summary>

Verification result callback interface.

**Methods:**

**`onSuccess(result: SelvageVerificationResult)`**: Called when the verification flow completes successfully.

* **Parameters:** `result: SelvageVerificationResult` : Result object delivered on successful verification.

***

**`onError(error: SelvageVerificationError)`**: Called when the verification flow fails.

* **Parameters:** `error: SelvageVerificationError`: Error object describing the failure.

***

**`onCancelled()`**: Called when the user cancels the verification flow.

* **Parameters:** None.

</details>

<details>

<summary><code>SelvageVerificationResult</code></summary>

Result object delivered via `SelvageSdk.Callback.onSuccess` when the verification flow completes successfully.

***

**Properties:**

| Property     | Type   | Description                |
| ------------ | ------ | -------------------------- |
| `sessionId`  | String | Session ID                 |
| `proofsJson` | String | Proof data in JSON format. |

***

**Example: Handling a successful verification result**

```kotlin
override fun onSuccess(result: SelvageVerificationResult) {
    val sessionId = result.sessionId
    val proofs = result.proofsJson
    // Send proofs to the backend for verification
    sendToBackend(sessionId, proofs)
}
```

</details>

***

## iOS Native Integration

### Step 1. `Podfile` Integration

Add the following to your `Podfile`:

```bash
source '<https://github.com/zkMeLabs/zktls-specs.git>'
source '<https://cdn.cocoapods.org/>'

platform :ios, '13.0'
use_frameworks! :linkage => :static

target 'YourApp' do
  pod 'SelvageVerifierSDK', '1.0.0'
end
```

### Step 2. Initial Setup

```bash
# 1. Add the private source to the local repository
pod repo add zktls-specs <https://github.com/zkMeLabs/zktls-specs.git >

# 2. Update the source
pod repo update zktls-specs

# 3. Install Pods
pod install
```

### Step 3. iOS Configuration

#### 3.1 Info.plist

Ensure the following network security configuration is included in your `Info.plist`:

```xml
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
```

#### 3.2 Build Settings

In your Xcode project’s **Build Settings**, configure the following:

| Setting                            | Value                   | Description                                      |
| ---------------------------------- | ----------------------- | ------------------------------------------------ |
| **Enable Bitcode**                 | No                      | Flutter does not support Bitcode.                |
| **Build Active Architecture Only** | Debug: Yes, Release: No | Ensure Release builds include all architectures. |
| **Excluded Architectures (Debug)** | i386                    | Exclude 32-bit emulator architectures.           |

### Step 4. Start Verification on iOS

{% tabs %}
{% tab title="Swift" %}
{% code expandable="true" %}

```swift
import UIKit
import SelvageVerifierSDK

class ViewController: UIViewController {
    
    @IBAction func startVerificationTapped(_ sender: UIButton) {
        startVerification()
    }
    
    private func startVerification() {
        SelvageSdk.startVerification(
            presenter: self,                  // Optional; if not provided, the currently displayed ViewController will be automatically located
            appId: "your-app-id",           
            apiKey: "your-api-key",         
            providerId: "your-provider-id"
        ) { [weak self] result in
            // The callback is executed on the main thread
            switch result {
            case .success(let verificationResult):
                // Verification successful
                print("Verification Successful")
                print("Session ID: \(verificationResult.sessionId)")
                print("Proofs JSON: \(verificationResult.proofsJson)")
                
                // Send proofsJson to the backend for verification
                self?.sendProofsToBackend(verificationResult.proofsJson)
                
            case .failure(let error):
                // Verification failed or canceled
                print("Verification Failed: \(error.localizedDescription)")
                self?.handleVerificationError(error)
            }
        }
    }
    
    private func sendProofsToBackend(_ proofsJson: String) {
        // Implement your backend verification logic
    }
    
    private func handleVerificationError(_ error: Error) {
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### API Reference

<details>

<summary><code>SelvageSdk</code></summary>

The main entry class of the SDK, providing static methods.

**Method:** `startVerificationWithAuth`

**Parameters:**

<table><thead><tr><th width="110.2935791015625">Name</th><th width="160.6097412109375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>presenter</code></td><td>UIViewController?</td><td>The view controller used to present the verification interface. If <code>nil</code>, the SDK automatically locates the currently displayed view controller.</td></tr><tr><td><code>appId</code></td><td>String</td><td>Application ID issued by zkTLS.</td></tr><tr><td><code>apiKey</code></td><td>String</td><td>API key associated with your application.</td></tr><tr><td><code>providerId</code></td><td>String</td><td>Provider identifier for the verification flow.</td></tr><tr><td><code>callback</code></td><td>Callback</td><td>Callback interface used to receive verification results.</td></tr></tbody></table>

**Return Value:** None.

</details>

<details>

<summary><code>VerificationResult</code></summary>

Result object delivered via the verification success callback when the verification flow completes successfully.

**Properties:**

| Property     | Type   | Description                                                                                    |
| ------------ | ------ | ---------------------------------------------------------------------------------------------- |
| `sessionId`  | String | Unique identifier of the verification session.                                                 |
| `proofsJson` | String | Proof payload in JSON format. This value should be submitted to your backend for verification. |

**Example: Handling a successful verification result**

```swift
case .success(let result):
    print("Session ID: \(result.sessionId)")
    print("Proofs JSON: \(result.proofsJson)")

    // Parse proofs JSON if needed
    if let data = result.proofsJson.data(using: .utf8),
       let json = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] {
        print("Parsed proofs array: \(json)")
    }
```

</details>

<details>

<summary><code>VerificationError</code></summary>

Error type delivered via the verification failure callback when the verification flow fails. This error object describes the reason why the verification did not complete successfully.

***

**Error Cases:**

* `invalidArguments(message)`\
  Indicates that one or more required arguments are missing or invalid.
* `sdkBusy`\
  Indicates that the SDK is currently processing another verification request.
* `verificationFailed(message, _, type)`\
  Indicates that the verification process completed but did not meet the required verification criteria.
* `flutterChannelError(code, message, _)`\
  Indicates an internal error occurred while communicating with the Flutter platform channel.

***

**Example: Handling a verification failure**

```swift
case .failure(let error):
    if let sdkError = error as? SelvageSdk.VerificationError {
        switch sdkError {

        case .invalidArguments(let message):
            // Handle invalid or missing arguments

        case .sdkBusy:
            // Handle SDK busy state

        case .verificationFailed(let message, _, let type):
            // Handle verification failure

        case .flutterChannelError(let code, let message, _):
            // Handle Flutter channel communication error

        default:
            // Handle other errors
        }
    }
```

</details>

***

## FAQs

This section collects common questions and their solutions that developers may encounter while using the SDK.

<details>

<summary>How to handle network disconnection?</summary>

The SDK will throw a `zkTlsException` upon network disconnection. Developers should catch this exception at the application layer and prompt the user to check their network connection or retry later.

</details>

<details>

<summary>Why did my data collection request fail?</summary>

Please check the following: Is the API Key correct? Is the zkTLS backend service address reachable? Is the Provider Schema valid? Is the target URL accessible? Detailed error messages can be obtained by enabling SDK logging.

</details>

<details>

<summary>Does the SDK support data collection from all websites?</summary>

The zkTLS protocol theoretically supports all TLS-based HTTPS websites. However, the specific data extraction capability depends on the definition of the Provider Schema and the website structure. For complex websites or those with dynamic content, customized Provider Schemas may be required. It is recommended to thoroughly test target websites before integration.

</details>
