---
title: Kindly Android SDK — Events Reference
slug: sdk/android-sdk-skill-events
description: Complete reference for the Kindly Android SDK event system. Use when handling KindlyEvent, KindlyEventDetail, KindlyEventMessage, KindlyEventButton, or the KindlySDK.events/KindlySDK.state Kotlin Flows and you need to know which fields are available on…
createdAt: 2026-11-04T11:39:56.001Z
---

Deep reference for `KindlyEvent` and every payload type in the Android SDK event system. Companion to [SKILL.md](https://docs.kindly.ai/sdk/android-sdk-skill.md) — use this when an agent needs to read or react to fields beyond what SKILL.md covers.

All types listed here live in `no.kindly.chatsdk.chat.models` and are public Kotlin `data class`es / `sealed class`es.

***

## Subscribing

Two Kotlin Flows are exposed:

```kotlin
KindlySDK.events: Flow<KindlyEvent>            // all events
KindlySDK.state:  Flow<KindlyEventState>       // state-only convenience
```

Typical subscription from a `CoroutineScope` (lifecycleScope, viewModelScope, …):

```kotlin
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import no.kindly.chatsdk.chat.models.*

lifecycleScope.launch {
    KindlySDK.events.collect { event ->
        when (val detail = event.detail) {
            is KindlyEventDetail.Load        -> handleLoad(detail.settings)
            is KindlyEventDetail.Message     -> handleMessage(detail.newMessage, detail.chatLog)
            is KindlyEventDetail.ButtonClick -> handleButton(detail.button, detail.buttonType,
                                                             detail.chatLog, detail.lastMessage)
            is KindlyEventDetail.UI          -> handleUI(detail.action)
            is KindlyEventDetail.GlobalIcon  -> handleGlobalIcon(detail.action)
            is KindlyEventDetail.State       -> handleState(detail.state)
        }
    }
}
```

`KindlySDK.events` is a `MutableSharedFlow` with `replay = 10`, so a late subscriber receives the last 10 events.

***

## Event Types at a Glance

| `KindlyEventType` | `value`                | `detail` subclass               | Payload                                                                            |
| ----------------- | ---------------------- | ------------------------------- | ---------------------------------------------------------------------------------- |
| `LOAD`            | `"kindly:load"`        | `KindlyEventDetail.Load`        | `KindlyEventSettings`                                                              |
| `MESSAGE`         | `"kindly:message"`     | `KindlyEventDetail.Message`     | `KindlyEventMessage` + `List<KindlyEventMessage>`                                  |
| `BUTTON_CLICK`    | `"kindly:buttonclick"` | `KindlyEventDetail.ButtonClick` | `KindlyEventButton` + `String` + `List<KindlyEventMessage>` + `KindlyEventMessage` |
| `UI`              | `"kindly:ui"`          | `KindlyEventDetail.UI`          | `KindlyEventUIAction`                                                              |
| `GLOBAL_ICON`     | `"kindly:globalicon"`  | `KindlyEventDetail.GlobalIcon`  | `KindlyEventGlobalIcon` — defined but not emitted on mobile                        |
| `STATE`           | `"kindly:state"`       | `KindlyEventDetail.State`       | `KindlyEventState`                                                                 |

***

## `KindlyEvent`

```kotlin
data class KindlyEvent(
    val type: KindlyEventType,
    val detail: KindlyEventDetail,
    val timestamp: Date = Date(),
)
```

## `KindlyEventDetail`

```kotlin
sealed class KindlyEventDetail {
    data class Load(val settings: KindlyEventSettings) : KindlyEventDetail()

    data class Message(
        val newMessage: KindlyEventMessage,
        val chatLog: List<KindlyEventMessage>,
    ) : KindlyEventDetail()

    data class ButtonClick(
        val button: KindlyEventButton,
        val buttonType: String,
        val chatLog: List<KindlyEventMessage>,
        val lastMessage: KindlyEventMessage,
    ) : KindlyEventDetail()

    data class UI(val action: KindlyEventUIAction) : KindlyEventDetail()

    data class GlobalIcon(val action: KindlyEventGlobalIcon) : KindlyEventDetail()

    data class State(val state: KindlyEventState) : KindlyEventDetail()
}
```

***

## Payloads

### `KindlyEventSettings` — `KindlyEventDetail.Load`

Configuration loaded from the backend after `start()` connects.

| Field       | Type                        | Notes                            |
| ----------- | --------------------------- | -------------------------------- |
| `name`      | `String`                    | Bot / workspace name             |
| `id`        | `String`                    | Settings ID                      |
| `languages` | `List<KindlyEventLanguage>` | Languages the bot supports       |
| `style`     | `KindlyEventStyle`          | API-provided theme (hex strings) |

### `KindlyEventMessage` — inside `Message` / `ButtonClick` / chatLog

```kotlin
data class KindlyEventMessage(
    val id: String,
    val text: String? = null,
    val sender: KindlyEventMessageSender,
    val created: Date? = null,
    val isFromUser: Boolean,
    val exchangeID: String? = null,
    val videoSource: String? = null,
    val exchangeType: KindlyEventExchangeType? = null,
    val buttons: List<KindlyEventButton> = emptyList(),
    val images: List<KindlyEventImage> = emptyList(),
    val attachments: List<KindlyEventAttachment> = emptyList(),
)
```

- `text` is `null` for messages that carry only buttons, images, or attachments.
- `isFromUser` is a convenience shortcut for `sender == KindlyEventMessageSender.USER`.
- `exchangeID` / `exchangeType` identify which dialogue node produced the message.

### `KindlyEventButton` — inside `ButtonClick` / `KindlyEventMessage.buttons`

```kotlin
data class KindlyEventButton(
    val id: String? = null,
    val type: String,                  // e.g. "quickreply", "postback", "url"
    val label: String,                 // text shown on the button
    val value: String? = null,         // payload; interpretation depends on `type`
    val index: Int? = null,            // position in the button list
    val hasBeenSelected: Boolean = false,
    val context: Map<String, Any>? = null,
)
```

### `KindlyEventUIAction` — `KindlyEventDetail.UI`

```kotlin
data class KindlyEventUIAction(
    val type: KindlyUIActionType,
    val source: String = "user",       // "user" or "programmatic"
    val chatId: String,
    val value: Map<String, Any>? = null,
    val feedbackType: String? = null,         // SHOW/SUBMIT/DISMISS_FEEDBACK only
    val feedbackValue: Map<String, Any>? = null,
    val languageCode: String? = null,         // CHANGE_LANGUAGE only
    val languageName: String? = null,
    val previousLanguageCode: String? = null,
    val previousLanguageName: String? = null,
    val nextPage: String? = null,
)
```

Most fields are context-specific — populated only when `type` requires them (see table below).

### `KindlyEventGlobalIcon` — `KindlyEventDetail.GlobalIcon`

```kotlin
data class KindlyEventGlobalIcon(
    val type: String,
    val source: String = "user",
    val value: Map<String, Any>? = null,
)
```

Defined for API parity with the web widget. Not currently emitted by the Android SDK.

### `KindlyEventState` — `KindlyEventDetail.State`

```kotlin
data class KindlyEventState(
    val isChatDisplayed: Boolean,      // chat UI is on screen
    val isStarted: Boolean,            // KindlySDK.start(...) completed
    val isSocketConnected: Boolean,    // Pusher WebSocket is live
    val isConnected: Boolean,          // isStarted && isSocketConnected
    val connectionState: String,       // "connected", "connecting", "disconnected", etc.
)
```

Use `isConnected` as the single "safe to send messages / trigger dialogues" flag.

***

## Supporting Types

### `KindlyEventLanguage`

```kotlin
data class KindlyEventLanguage(
    val code: String,         // "en", "sv", "fr", ...
    val name: String,         // "English", "Swedish", ...
    val isDefault: Boolean = false,
)
```

### `KindlyEventStyle`

API-provided theme values (hex strings). All optional — fields are `null` when the backend hasn't configured a value.

| Field                   | Type      |
| ----------------------- | --------- |
| `background`            | `String?` |
| `buttonBackground`      | `String?` |
| `buttonText`            | `String?` |
| `headerBackground`      | `String?` |
| `headerText`            | `String?` |
| `inputBackground`       | `String?` |
| `inputText`             | `String?` |
| `userMessageBackground` | `String?` |
| `userMessageTextColor`  | `String?` |
| `botMessageBackground`  | `String?` |
| `botMessageTextColor`   | `String?` |

### `KindlyEventImage`

```kotlin
data class KindlyEventImage(
    val url: String,
    val altText: String? = null,
    val id: String? = null,
)
```

### `KindlyEventAttachment`

```kotlin
data class KindlyEventAttachment(
    val id: String,
    val name: String,
    val format: String,         // e.g. "pdf", "png"
    val status: String,         // e.g. "uploaded", "pending"
    val sender: String,
    val storagePath: String,    // remote storage path
)
```

### `KindlyEventMessageSender` (enum)

| Case      | `value`     |
| --------- | ----------- |
| `AGENT`   | `"AGENT"`   |
| `BOT`     | `"BOT"`     |
| `SYSTEM`  | `"SYSTEM"`  |
| `USER`    | `"USER"`    |
| `UNKNOWN` | `"UNKNOWN"` |

### `KindlyEventExchangeType` (enum)

| Case              | `value`             |
| ----------------- | ------------------- |
| `DIALOGUE`        | `"dialogue"`        |
| `FALLBACK`        | `"fallback"`        |
| `GREETING`        | `"greeting"`        |
| `SYSTEM_DIALOGUE` | `"system_dialogue"` |
| `USER_SAYS`       | `"user_says"`       |

### `KindlyUIActionType` (enum)

Full list of UI actions emitted via `KindlyEventDetail.UI`:

| Case                | `value`              | When it fires                  | Extra fields you can expect                                                    |
| ------------------- | -------------------- | ------------------------------ | ------------------------------------------------------------------------------ |
| `OPEN_CHAT_BUBBLE`  | `"open-chatbubble"`  | Chat UI presented              | —                                                                              |
| `CLOSE_CHAT_BUBBLE` | `"close-chatbubble"` | Chat UI dismissed              | —                                                                              |
| `END_CHAT`          | `"end-chat"`         | Session ended                  | —                                                                              |
| `RESTART_CHAT`      | `"restart-chat"`     | User restarted the session     | —                                                                              |
| `DOWNLOAD_CHAT`     | `"download-chat"`    | User downloaded the transcript | —                                                                              |
| `DELETE_CHAT`       | `"delete-chat"`      | Chat history cleared           | —                                                                              |
| `CHANGE_LANGUAGE`   | `"change-language"`  | Language switched              | `languageCode`, `languageName`, `previousLanguageCode`, `previousLanguageName` |
| `SHOW_FEEDBACK`     | `"show-feedback"`    | Feedback prompt shown          | `feedbackType`                                                                 |
| `SUBMIT_FEEDBACK`   | `"submit-feedback"`  | Feedback submitted             | `feedbackType`, `feedbackValue`                                                |
| `DISMISS_FEEDBACK`  | `"dismiss-feedback"` | Feedback dismissed             | `feedbackType`                                                                 |

***

## Worked Examples

### React to a bot reply only

```kotlin
lifecycleScope.launch {
    KindlySDK.events
        .mapNotNull { event ->
            (event.detail as? KindlyEventDetail.Message)
                ?.takeIf { it.newMessage.sender == KindlyEventMessageSender.BOT }
                ?.newMessage
        }
        .collect { botMessage ->
            Log.d("Kindly", "Bot: ${botMessage.text.orEmpty()}")
        }
}
```

### Log every button the user taps

```kotlin
lifecycleScope.launch {
    KindlySDK.events.collect { event ->
        val click = event.detail as? KindlyEventDetail.ButtonClick ?: return@collect
        Log.d("Kindly",
            "User tapped [${click.buttonType}] ${click.button.label} → value=${click.button.value}"
        )
    }
}
```

### Track language switches

```kotlin
lifecycleScope.launch {
    KindlySDK.events
        .mapNotNull { (it.detail as? KindlyEventDetail.UI)?.action }
        .filter { it.type == KindlyUIActionType.CHANGE_LANGUAGE }
        .collect { action ->
            val from = action.previousLanguageCode ?: return@collect
            val to = action.languageCode ?: return@collect
            analytics.log("language_changed", mapOf("from" to from, "to" to to))
        }
}
```

### Wait for connection, then trigger a dialogue

```kotlin
lifecycleScope.launch {
    KindlySDK.state
        .filter { it.isConnected }
        .first()
    KindlySDK.triggerDialogue(id = "welcome_flow")
}
```

### Collect the full chat transcript on demand

```kotlin
lifecycleScope.launch {
    KindlySDK.events
        .mapNotNull { (it.detail as? KindlyEventDetail.Message)?.chatLog }
        .collect { transcript ->
            currentTranscript = transcript
        }
}
```

***

## Threading Notes

- Both flows emit on whichever dispatcher the SDK processes the event from.
- For UI work, switch dispatchers explicitly with `.flowOn(Dispatchers.Main)` or collect from `lifecycleScope` (already main).

```kotlin
lifecycleScope.launch {
    KindlySDK.state.collect { state ->
        statusTextView.text = state.connectionState
    }
}
```
