---
name: knot-sdk
description: >
  Install and initialize the Knot SDK to enable merchant account linking. Covers iOS,
  Android, React Native, Flutter, and Web. Use when: (1) "install the Knot SDK",
  (2) "initialize the SDK", (3) "set up the Knot SDK", (4) "integrate the Knot SDK",
  (5) "add the Knot SDK to my app", (6) "configure KnotConfiguration",
  (7) "handle SDK events".
metadata:
  author: Knot
  version: "1.0"
---

# Knot SDK

Install and initialize the Knot SDK to enable merchant account linking. The SDK handles the full authentication flow, including credential entry and step-up authentication (MFA).

For shared setup (credentials, base URLs, `Knot-Version`) and other products, see the `knot` skill.

## Before Starting: Load API Context

If the Knot docs MCP server (`https://docs.knotapi.com/mcp`) is connected, use its search tool to read the SDK page. Otherwise fetch the page as Markdown (append `.md`), or use https://docs.knotapi.com/llms.txt as an index. Only fetch the page for the platform being implemented:

- https://docs.knotapi.com/sdk/ios.md
- https://docs.knotapi.com/sdk/android.md
- https://docs.knotapi.com/sdk/react-native.md
- https://docs.knotapi.com/sdk/flutter.md
- https://docs.knotapi.com/sdk/web.md

To add the MCP server, the user can run `npx add-mcp https://docs.knotapi.com/mcp --name knot-docs`. If neither the MCP server nor web fetch is available, the workflow below contains everything needed.

## Workflow

### Step 1: Install the SDK

**iOS (CocoaPods):**
```ruby
pod 'KnotAPI'
```

**iOS (Swift Package Manager):**
Add `https://github.com/millionscard/knot-api-ios` with "Up to Next Major Version" rule and link the `KnotAPI` product. Requires Swift 5.3+.

`KnotAPI` is a dynamic xcframework. Xcode's app templates already set `LD_RUNPATH_SEARCH_PATHS = @executable_path/Frameworks`. If the project was generated some other way (hand-written `project.pbxproj`, custom build settings) and that setting is missing, the app crashes on launch with `dyld: Library not loaded: @rpath/KnotAPI.framework/KnotAPI`.

**Android (Gradle):**
```groovy
dependencies {
    implementation 'com.knotapi.knot:knotapi-android-sdk:<latest-version>'
}
```
Requires minSdkVersion 21 and Java 8.

**React Native:**
```bash
npm install react-native-knotapi --save
```

**Flutter:**
```bash
flutter pub add knotapi_flutter
```

**Web (npm):**
```bash
npm install knotapi-js@next --save
```

**Web (CDN):**
```html
<script src="https://unpkg.com/knotapi-js@next"></script>
```

### Step 2: Configure and open the SDK

The SDK requires a `session_id` from `POST /session/create` (called from the backend) and the `client_id` from the Knot Dashboard. Send a `Knot-Version: 2.0` header on the backend API call; if omitted, the API defaults to `2.0`, but sending it explicitly is recommended.

**Required parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `sessionId` | string | From backend `POST /session/create` |
| `clientId` | string | From Knot Dashboard, differs per environment |
| `environment` | string/enum | `development` or `production` |

The `sessionId`, `clientId`, and `environment` all come from the same environment: a development session opens with the development client ID and `environment: development`.

**Optional parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `merchantIds` | int[] | none | Required for transaction data use cases. Otherwise, recommended to leave empty so the SDK displays its built-in merchant list. Pass a single merchant ID when displaying merchants natively in the app and the user selects one before opening the SDK. |
| `entryPoint` | string | none | **Strongly recommended.** Identifier for where the user invoked the SDK (e.g. "onboarding", "home", "settings"). Returned in the AUTHENTICATED webhook, enabling conversion analytics by entry point. |
| `useCategories` | boolean | true | Show merchant categories in the SDK UI |
| `useSearch` | boolean | true | Show search bar in the SDK UI |
| `metadata` | object | none | Key-value pairs (max 10 keys, 500 chars per value) echoed in webhook payloads |
| `locale` | string | "en-US" | BCP-47 tag. Supported on every platform: `en-US`, `es-US`, `en-CA`, and `fr-CA`. |

**iOS (Swift):**
```swift
import KnotAPI

let configuration = KnotConfiguration(
    sessionId: sessionId,
    clientId: clientId,
    environment: .development,
    entryPoint: "onboarding",
    useCategories: true,
    useSearch: true,
    merchantIds: nil
)
Knot.open(configuration: configuration, delegate: self)
```

In Swift, `useCategories`, `useSearch`, and `merchantIds` have no defaults and must be passed (verified against SDK 2.1.3). `customerConfiguration`, `locale`, and `metadata` are optional and go after `merchantIds`.

**Android (Kotlin):**

`KnotConfiguration` takes positional arguments in this order (from the Android SDK docs):
```kotlin
import com.knotapi.knot.*

val config = KnotConfiguration(
    sessionId,                            // sessionId
    clientId,                             // clientId
    Environment.PRODUCTION,               // environment
    null, null, null, null,               // merchantIds, useCategories, useSearch, domainUrls (optional)
    "onboarding",                         // entryPoint
    null, null                            // customerConfiguration, locale (optional)
)
Knot.open(context, config, eventDelegate)
```

**React Native:**
```javascript
import { Knot } from "react-native-knotapi";

Knot.open({
    sessionId: sessionId,
    clientId: clientId,
    environment: "development",
    entryPoint: "onboarding"
});
```

**Flutter:**
```dart
import 'package:knotapi_flutter/knotapi_flutter.dart';
import 'package:knotapi_flutter/knotapi_configuration.dart';

final knot = KnotapiFlutter();
knot.open(KnotConfiguration(
    sessionId: sessionId,
    clientId: clientId,
    environment: Environment.development,
    entryPoint: "onboarding"
));
```

**Web:**
```javascript
import KnotapiJS from "knotapi-js";
// With the CDN script tag instead: const KnotapiJS = window.KnotapiJS.default;
const knotapi = new KnotapiJS();

// Every Web callback receives one object, not positional arguments
knotapi.open({
    sessionId: sessionId,
    clientId: clientId,
    environment: "development",
    entryPoint: "onboarding",
    onSuccess: ({ merchant }) => {},
    onError: ({ errorCode, errorDescription }) => {},
    onEvent: ({ event, merchant, merchantId, taskId, metaData, product, environment }) => {},
    onExit: () => {}
});
```

The Web SDK renders in an iframe whose `frame-ancestors` policy allows `https://localhost:*` but not `http://localhost`. For local development, serve your page over HTTPS (a self-signed or `mkcert` certificate, or an HTTPS tunnel). On plain `http://localhost` the SDK shows an endless spinner and `onError` never fires.

### Step 3: Handle events

The SDK emits events during the authentication flow. Use them to update your UI. Drive backend state from webhooks, which arrive even if the app closes mid-flow.

**Event callbacks:**

| Callback | Trigger | Key data |
|----------|---------|----------|
| `onSuccess` | User successfully authenticated | `merchant` name (on Web, a `{ event, merchant }` object) |
| `onError` | SDK initialization failed | `errorCode`, `errorDescription` |
| `onExit` | User closed the SDK | none |
| `onEvent` | Lifecycle events during the flow | `event`, `merchant`, `merchantId`, `taskId`, `metaData` |

**Lifecycle events (via onEvent):**

| Event | Description |
|-------|-------------|
| `MERCHANT_CLICKED` | User selected a merchant |
| `LOGIN_STARTED` | User submitted credentials |
| `AUTHENTICATED` | Login succeeded |
| `OTP_REQUIRED` | MFA code requested |
| `REFRESH_SESSION_REQUEST` | Session expiring in ~5 seconds. Call the Extend Session API. |

**Error codes (via onError):**

| Code | Cause |
|------|-------|
| `INVALID_SESSION` | Session ID is invalid or was created for a different environment |
| `EXPIRED_SESSION` | Session is older than 30 minutes |
| `INVALID_CLIENT_ID` | Client ID does not match the environment |
| `INTERNAL_ERROR` | Internal SDK error. Retry with a new session. |
| `MERCHANT_ID_NOT_FOUND` | The session type requires a merchant ID in `merchantIds` and none was provided |
| `INVALID_CARD_NAME` | `cardName` (customer configuration) does not exactly match a value Knot allowlisted for your organization |
| `INVALID_CUSTOMER_NAME` | `customerName` (customer configuration) does not exactly match an allowlisted value |
| `INVALID_LOGO_ID` | `logoId` (customer configuration) does not exactly match an allowlisted value |
| `INVALID_LOCALE` | `locale` is not a valid, supported BCP-47 tag for that platform |

Casing differs by platform: iOS uses enum cases (`.invalidSession`, `.invalidLocale`, ...), React Native uses `Invalid_Session`, `Invalid_Locale`, etc., and Android, Flutter, and Web use the upper-case codes above.

**Customer configuration (optional, rarely used):** `cardName`, `customerName`, and `logoId` override the card name, company name, and logo shown in the SDK, typically when one app offers several differently named card programs. Knot must pre-define the allowed values; any other value triggers the `INVALID_*` errors above. It is passed as `CustomerConfiguration` on iOS, Android, and Flutter, `customerConfig: { ... }` on React Native, and top-level `cardName`/`customerName`/`logoId` on Web.

**Event handling by platform:**

- **iOS:** Implement the `KnotEventDelegate` protocol with `onSuccess`, `onError`, `onExit`, `onEvent` methods
- **Android:** Implement the `KnotEventDelegate` interface with the same methods
- **React Native:** Use `addKnotListener('knot:onSuccess', callback)`, `addKnotListener('knot:onError', callback)`, etc.
- **Flutter:** Subscribe to streams: `KnotapiFlutter.onSuccess.listen(callback)`, `KnotapiFlutter.onError.listen(callback)`, etc.
- **Web:** Pass callbacks inline to `knotapi.open({ onSuccess, onError, onEvent, onExit })`

**Cross-platform onEvent differences:**

| Property | iOS | Android | React Native | Flutter | Web |
|----------|-----|---------|-------------|---------|-----|
| event name | `event` | `getEvent()` | `event` | `event` | `event` |
| merchant | `merchant` | `getMerchantName()` | `merchantName` | `merchant` | `merchant` |
| merchant ID | `merchantId` | `getMerchantId()` | `merchantId` | `merchantId` | `merchantId` |
| metadata | `metaData` (NSDictionary) | `getMetaData()` (Map) | `metaData` (Record) | `metaData` (Map) | `metaData` (object) |
| task ID | `taskId` | `getTaskId()` | `taskId` | `taskId` | `taskId` |
| environment | `environment` | `getEnvironment()` | `environment` | `environment` | `environment` |
| product | | | | | `product` (session type, e.g. `card_switcher`) |
| Security questions event | `SECURITY_QUESTIONS_REQUIRED` | `SECURITY_QUESTIONS_REQUIRED` | `SECURITY_QUESTIONS_REQUIRED` | `SECURITY_QUESTIONS_REQUIRED` | `QUESTIONS_REQUIRED` |

### Step 4: Session management

- Sessions expire after 30 minutes. A session expiring while the user has the SDK open is exceptionally rare and not required to implement.
- Create a new session for each SDK invocation
- Optionally listen for the `REFRESH_SESSION_REQUEST` event (~5 seconds before expiry) and call the Extend Session API to keep the session alive

### Step 5: Close the SDK

To programmatically close the SDK:

- **iOS:** `Knot.close()`
- **Android:** `Knot.close()`
- **React Native:** `Knot.close()` (dismiss the screen hosting the SDK once)
- **Flutter:** `knot.close()`
- **Web:** Not applicable (user closes the modal)

## Web SDK: Domain Allowlisting

For the Web SDK, application domains can be allowlisted in the Knot Dashboard at https://dashboard.knotapi.com/developers/domains. Ask the Knot team to enable this feature before configuring domains in the dashboard. When enabled, only allowlisted domains can invoke the SDK.
