---
title: React Native SDK
slug: react-native-sdk
createdAt: 2026-11-03T10:14:12.866Z
---

The official **Kindly Chat SDK** for React Native — a thin TurboModule
wrapper around the public iOS xcframework and Android AAR. The native
binaries ship inside the npm tarball, so customers don't need Swift Package
Manager, Maven Central, or any extra Gradle config.

:::BlockQuote
**Status:** Early access. Targets React Native New Architecture
(TurboModules + Codegen). Requires RN ≥ 0.76.
:::

## Installation

```shell
yarn add @kindly-ai/react-native
cd ios && pod install
```

That's it — no extra setup. The bundled iOS framework is pre-signed with the
Apple Distribution cert and passes App Store ITMS-91065 validation.

## Usage

```typescript
import { KindlySDK, hexColor } from '@kindly-ai/react-native';

// 1. Initialize once on app start
KindlySDK.start({
  botKey: 'your-bot-key',
  market: 'general', // required — your market slug (Settings → General → Details & markets)
  languageCode: 'en',
  // Optional: invoked by the SDK when it needs an authenticated JWT for
  // the current chat session. `chatId` is the backend-generated identifier
  // — include it when calling your auth backend so the token is scoped to
  // this chat. Return null for anonymous chats.
  authTokenCallback: async (chatId) => {
    return await yourAuthBackend.getKindlyJWT({ chatId });
  },
});

// Identity providers that issue more than a single JWT - a companion access
// token, an issuer, or an opaque token whose expiry only you know - use
// authCredentialsCallback instead. It wins when both are given.
KindlySDK.start({
  botKey: 'YOUR_BOT_KEY',
  market: 'general',
  authCredentialsCallback: async (chatId) => {
    const tokens = await yourAuthBackend.getTokens({ chatId });
    return {
      idToken: tokens.idToken,             // optional
      accessToken: tokens.accessToken,     // optional
      issuer: 'https://idp.example.com/',  // optional
      expiresAt: tokens.expiresAtUnixSeconds, // optional, Unix seconds
    };
  },
});

// Only an access token? Return only that. It becomes the bearer.
KindlySDK.start({
  botKey: 'YOUR_BOT_KEY',
  market: 'general',
  authCredentialsCallback: async () => ({
    accessToken: await yourAuthBackend.getAccessToken(),
    issuer: 'https://idp.example.com/',
  }),
});

// 2. Open the chat UI when the user wants it
<Button title="Open chat" onPress={() => KindlySDK.launchChat()} />

// 3. (Optional) host-app callbacks for buttons / link interception
KindlySDK.setDelegate({
  onButtonPressed: (button, chatLog) => console.log('button', button),
  shouldHandleLink: (url) => {
    if (url.startsWith('myapp://')) {
      // Take over the deep link
      return false;
    }
    return true; // let the SDK open it
  },
});
```

## API

### Lifecycle

| Method                                                                                 | Purpose                                                                                                                                                                                                                                                |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `start({botKey, market, languageCode?, authTokenCallback?, authCredentialsCallback?})` | Initialize the SDK. Must be called before any other method. `market` is **required** (Markets feature); the SDK loads that market's settings with no fallback. Use `authCredentialsCallback` for identity providers that issue more than a single JWT. |
| `displayChat({languageCode?, triggerDialogueId?})`                                     | Show the chat UI.                                                                                                                                                                                                                                      |
| `launchChat({triggerDialogueId?})`                                                     | Connect (if needed) and show chat. Recommended on Android.                                                                                                                                                                                             |
| `closeChat()`                                                                          | Dismiss the UI without ending the session.                                                                                                                                                                                                             |
| `endChat()`                                                                            | End the chat session: clears messages, token, dismisses UI.                                                                                                                                                                                            |
| `kill()`                                                                               | Tear the SDK down completely; call `start()` again before reuse.                                                                                                                                                                                       |
| `setLanguage(code)`                                                                    | Update the language for the current session.                                                                                                                                                                                                           |

### Authentication

| Method             | Purpose                                                                                                                   |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `authenticate()`   | Re-authenticate the live chat with fresh credentials. For a sign-in mid-session; expiry is handled by the SDK on its own. |
| `deauthenticate()` | Sign out of the chat without ending it. History is kept, the chat continues anonymously.                                  |

Every field of `KindlyAuthCredentials` is optional, but set at least one of `idToken` /
`accessToken`. Whichever you supply becomes the `Authorization: Bearer`, id token first. A provider
that issues only an access token is a normal, supported case, not a workaround. Credentials with
neither token cannot authenticate and the chat stays anonymous.

Send `accessToken` alongside `idToken` only when the two really are a pair from the same provider.
The backend verifies the companion against the id token's `at_hash` and rejects a mismatch, so
passing the same value in both fields fails an auth that would otherwise have succeeded. The SDK
drops a companion that duplicates the id token rather than sending it.

The SDK refreshes credentials 30 seconds before they expire, using whichever is earliest of the
backend's reported expiry, your `expiresAt`, and the `exp` claim of the bearer. If none is known the
credentials are treated as non-expiring - a trap for opaque tokens, so set `expiresAt` there. A
token the SDK cannot parse means the expiry is unknown, never that the token is expired.

`deauthenticate()` is safe to call when the chat is already anonymous. The local credentials are
cleared even if the backend call fails, so a sign-out is never left half done, but the promise still
rejects so you know the backend did not hear about it.

### Messaging

| Method                                           | Purpose                                                                                |
| ------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `sendMessage(text, {newContext?})`               | Send a message programmatically. Returns the reconciled message.                       |
| `setNewContext({...})` / `clearNewContext()`     | Stage / clear context attached to the next message.                                    |
| `triggerDialogue(id)` / `clearTriggerDialogue()` | Trigger a specific dialogue by id.                                                     |
| `handleUrl(url)`                                 | Handle a `kindly://chat/dialogue/{id}` deep link. Returns true if the URL was handled. |
| `callHandover(callback)`                         | Initiate a handover to a human agent.                                                  |

### Theme

| Method                | Purpose                                                         |
| --------------------- | --------------------------------------------------------------- |
| `setCustomTheme({…})` | Apply a custom theme. Pass undefined fields to keep defaults.   |
| `clearCustomTheme()`  | Revert to the default / API-supplied theme.                     |
| `hexColor(hex)`       | Helper: `#RRGGBB` / `#AARRGGBB` → ARGB int (used in the theme). |

### Push

| Method                         | Purpose                                                |
| ------------------------------ | ------------------------------------------------------ |
| `setAPNSDeviceToken(token)`    | iOS only — register the APNS device token.             |
| `saveNotificationToken(token)` | Android only — register the FCM token.                 |
| `handleNotification(data)`     | Forward an incoming push payload to the SDK.           |
| `isKindlyNotification(data)`   | Returns true if the given push originated from Kindly. |

### State / settings

| Method                       | Purpose                                                                             |
| ---------------------------- | ----------------------------------------------------------------------------------- |
| `isChatDisplayed()`          | Returns true if the chat UI is currently visible (iOS only — Android always false). |
| `saveAuthToken(token)`       | Save a JWT auth token (iOS keychain).                                               |
| `setVerboseLogging(enabled)` | Toggle verbose SDK logging.                                                         |
| `setCrashReporting(enabled)` | Toggle Sentry crash reporting from inside the SDK.                                  |

### Delegate

| Method                                                                          | Purpose                        |
| ------------------------------------------------------------------------------- | ------------------------------ |
| `setDelegate({onButtonPressed?, shouldHandleLink?, shouldHandleNotification?})` | Register host-app callbacks.   |
| `clearDelegate()`                                                               | Remove all delegate callbacks. |

`shouldHandleLink` and `shouldHandleNotification` are awaited synchronously
by the native SDK with a 5-second timeout. After timeout the SDK falls back
to its default `true` (handle normally) so chat doesn't hang on a slow JS
callback.

## Compatibility

- **React Native:** 0.76+ (New Architecture only)
- **iOS:** 15.0+
- **Android:** API level 24+ (Android 7.0)

## License

MIT
