A Kotlin Multiplatform chat SDK for Chatwoot β one Compose chat page that runs natively on Android and iOS, backed by the Chatwoot website-widget API with live messages over websocket.
ChatPage(
show = showChat,
onFinish = { showChat = false },
styleConfig = DefaultStyle, // optional theming
)Android / KMP (Maven Central):
implementation("com.chatwoot.android:sdk:0.1.0")iOS (Swift Package Manager): add this repo as a package dependency β the
ChatwootSDK binary target ships as an XCFramework attached to GitHub releases.
Create a website inbox in your Chatwoot dashboard, then configure the SDK once at startup:
Chatwoot.configure(
baseUrl = "https://app.chatwoot.com", // or your self-hosted URL
websiteToken = "<website inbox token>",
)Or declare it statically instead:
- Android β manifest
<meta-data>:com.chatwoot.android.BASE_URLandcom.chatwoot.android.WEBSITE_TOKEN - iOS β Info.plist keys:
ChatwootBaseUrlandChatwootWebsiteToken
Then show the chat. On Android/Compose, call ChatPage (above). From Swift:
ChatPageViewControllerKt.ChatPageViewController(
onFinish: { /* dismiss */ },
styleConfig: StyleConfigKt.DefaultStyle
)Everything else β anonymous contact creation, conversation persistence across launches, history, live agent replies, typing indicators, reconnection β is handled inside.
Recording a voice note needs the microphone. The SDK requests it at first use and silently hides the mic button if it's unavailable.
- Android β
RECORD_AUDIOis declared in the SDK manifest and merges into your app automatically; nothing to add. - iOS β you must add
NSMicrophoneUsageDescription(with a user-facing reason) to your app'sInfo.plist. iOS aborts at permission-request time if the key is missing, and the SDK cannot supply it on your behalf.
Picking image/video/file attachments needs no runtime permission on either platform.
By default the contact is anonymous. If your app knows who the user is, identify them so agents
see a named contact and conversations follow the user across reinstalls/devices. Call it any
time (typically after your own login), before or while ChatPage is shown:
Chatwoot.setUser(
identifier = "your-user-id", // stable id; recognises the same person later
name = "Ada Lovelace",
email = "ada@example.com",
phoneNumber = "+15551234567",
customAttributes = mapOf("plan" to "pro"),
identifierHash = serverComputedHash, // only if the inbox enforces identity validation
)
Chatwoot.setCustomAttributes(mapOf("plan" to "enterprise")) // merge more attributes later
Chatwoot.reset() // on logout β forgets the sessionIdentity validation (optional): if you enable it on the inbox, identifierHash is required.
It must be computed on your backend as HMAC-SHA256(hmacToken, identifier) β the HMAC token
is a secret and must never ship inside the app. Passing a different identifier than the active
session starts a fresh contact + conversation on the next ChatPage open.
All visual customisation flows through one object:
ChatPage(show, onFinish, styleConfig = DefaultStyle.copy(
primaryColor = Color(0xFF7C3AED),
title = "Support",
))βββ sdk/ # the KMP library (commonMain + androidMain/iosMain)
βββ sample-app/ # Android demo β put chatwoot.baseUrl / chatwoot.websiteToken in local.properties
βββ iosApp/ # iOS demo (Xcode project, builds the framework via Gradle)
βββ docs/adr/ # architecture decision records
βββ CONTEXT.md # domain glossary + verified protocol contract
./gradlew build # everything incl. tests (needs macOS for iOS targets)
./gradlew :sample-app:installDebug # Android demo on a connected device
./gradlew :sdk:assembleChatwootSDKReleaseXCFramework # ChatwootSDK.xcframework for Swift apps
open iosApp/iosApp.xcodeproj # iOS demoMIT β see LICENSE.