---
title: Kindly iOS SDK — Events Reference
slug: sdk/ios-sdk-skill-events
description: Complete reference for the Kindly iOS SDK event system. Use when handling KindlyEvent, KindlyEventDetail, KindlyEventMessage, KindlyEventButton, or the KindlySDK.events/KindlySDK.state Combine publishers and you need to know which fields are available…
createdAt: 2026-11-04T11:40:16.459Z
---

Deep reference for `KindlyEvent` and every payload type in the iOS SDK event system. Companion to [SKILL.md](https://docs.kindly.ai/sdk/ios-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 the `Kindly` module and are `public`. Fields are `public let`.

***

## Subscribing

Two Combine publishers are exposed:

```swift
KindlySDK.events: AnyPublisher<KindlyEvent, Never>    // all events
KindlySDK.state:  AnyPublisher<KindlyEventState, Never>  // state-only convenience
```

Typical subscription:

```swift
import Combine

var cancellables = Set<AnyCancellable>()

KindlySDK.events
    .sink { event in
        switch event.detail {
        case .load(let settings):             handleLoad(settings)
        case .message(let new, let log):      handleMessage(new, log: log)
        case .buttonClick(let btn, let type, let log, let last):
                                              handleButton(btn, type: type, log: log, last: last)
        case .ui(let action):                 handleUI(action)
        case .globalIcon(let action):         handleGlobalIcon(action)
        case .state(let state):               handleState(state)
        }
    }
    .store(in: &cancellables)
```

***

## Event Types at a Glance

| `KindlyEventType` | raw value              | `detail` case                                          | Payload                                                                        |
| ----------------- | ---------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------ |
| `.load`           | `"kindly:load"`        | `.load(settings:)`                                     | `KindlyEventSettings`                                                          |
| `.message`        | `"kindly:message"`     | `.message(newMessage:chatLog:)`                        | `KindlyEventMessage` + `[KindlyEventMessage]`                                  |
| `.buttonClick`    | `"kindly:buttonclick"` | `.buttonClick(button:buttonType:chatLog:lastMessage:)` | `KindlyEventButton` + `String` + `[KindlyEventMessage]` + `KindlyEventMessage` |
| `.ui`             | `"kindly:ui"`          | `.ui(action:)`                                         | `KindlyEventUIAction`                                                          |
| `.globalIcon`     | `"kindly:globalicon"`  | `.globalIcon(action:)`                                 | `KindlyEventGlobalIcon` — defined but not emitted on mobile                    |
| `.state`          | `"kindly:state"`       | `.state(state:)`                                       | `KindlyEventState`                                                             |

***

## `KindlyEvent`

```swift
public struct KindlyEvent {
    public let type: KindlyEventType
    public let detail: KindlyEventDetail
    public let timestamp: Date   // when the event was emitted
}
```

## `KindlyEventDetail`

```swift
public enum KindlyEventDetail {
    case load(settings: KindlyEventSettings)
    case message(newMessage: KindlyEventMessage, chatLog: [KindlyEventMessage])
    case buttonClick(button: KindlyEventButton, buttonType: String,
                     chatLog: [KindlyEventMessage], lastMessage: KindlyEventMessage)
    case ui(action: KindlyEventUIAction)
    case globalIcon(action: KindlyEventGlobalIcon)
    case state(state: KindlyEventState)
}
```

***

## Payloads

### `KindlyEventSettings` — `.load`

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

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

### `KindlyEventMessage` — `.message` / inside `buttonClick` / inside chatLog

```swift
public struct KindlyEventMessage {
    public let id: String
    public let text: String?
    public let sender: KindlyEventMessageSender
    public let created: Date?
    public let isFromUser: Bool
    public let exchangeID: String?
    public let videoSource: String?
    public let exchangeType: KindlyEventExchangeType?
    public let buttons: [KindlyEventButton]
    public let images: [KindlyEventImage]
    public let attachments: [KindlyEventAttachment]
}
```

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

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

```swift
public struct KindlyEventButton {
    public let id: String?
    public let type: String        // e.g. "quickreply", "postback", "url"
    public let label: String       // text shown on the button
    public let value: String?      // payload; interpretation depends on `type`
    public let index: Int?         // position in the button list
    public let hasBeenSelected: Bool
    public let context: [String: Any]?
}
```

### `KindlyEventUIAction` — `.ui`

```swift
public struct KindlyEventUIAction {
    public let type: KindlyUIActionType
    public let source: String          // "user" or "programmatic"
    public let chatId: String
    public let value: [String: Any]?
    public let feedbackType: String?   // only for showFeedback/submitFeedback/dismissFeedback
    public let feedbackValue: [String: Any]?
    public let languageCode: String?           // only for changeLanguage
    public let languageName: String?
    public let previousLanguageCode: String?
    public let previousLanguageName: String?
    public let nextPage: String?
}
```

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

### `KindlyEventGlobalIcon` — `.globalIcon`

```swift
public struct KindlyEventGlobalIcon {
    public let type: String
    public let source: String
    public let value: [String: Any]?
}
```

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

### `KindlyEventState` — `.state`

```swift
public struct KindlyEventState {
    public let isChatDisplayed: Bool      // chat UI is on screen
    public let isStarted: Bool            // KindlySDK.start(...) completed
    public let isSocketConnected: Bool    // Pusher WebSocket is live
    public let isConnected: Bool          // isStarted && isSocketConnected
    public let connectionState: String    // "connected", "connecting", "disconnected", etc.
}
```

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

***

## Supporting Types

### `KindlyEventLanguage`

```swift
public struct KindlyEventLanguage {
    public let code: String       // "en", "sv", "fr", ...
    public let name: String       // "English", "Swedish", ...
    public let isDefault: Bool
}
```

### `KindlyEventStyle`

API-provided theme values (hex strings). All optional — fields nil 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`

```swift
public struct KindlyEventImage {
    public let url: String
    public let altText: String?
    public let id: String?
}
```

### `KindlyEventAttachment`

```swift
public struct KindlyEventAttachment {
    public let id: String
    public let name: String
    public let format: String       // e.g. "pdf", "png"
    public let status: String       // e.g. "uploaded", "pending"
    public let sender: String
    public let storagePath: String  // remote storage path
}
```

### `KindlyEventMessageSender` (enum)

| Case       | Raw value   |
| ---------- | ----------- |
| `.agent`   | `"AGENT"`   |
| `.bot`     | `"BOT"`     |
| `.system`  | `"SYSTEM"`  |
| `.user`    | `"USER"`    |
| `.unknown` | `"UNKNOWN"` |

### `KindlyEventExchangeType` (enum)

| Case              | Raw value           |
| ----------------- | ------------------- |
| `.dialogue`       | `"dialogue"`        |
| `.fallback`       | `"fallback"`        |
| `.greeting`       | `"greeting"`        |
| `.systemDialogue` | `"system_dialogue"` |
| `.userSays`       | `"user_says"`       |

### `KindlyUIActionType` (enum)

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

| Case               | Raw value            | When it fires                  | Extra fields you can expect                                                    |
| ------------------ | -------------------- | ------------------------------ | ------------------------------------------------------------------------------ |
| `.openChatBubble`  | `"open-chatbubble"`  | Chat UI presented              | —                                                                              |
| `.closeChatBubble` | `"close-chatbubble"` | Chat UI dismissed              | —                                                                              |
| `.endChat`         | `"end-chat"`         | Session ended                  | —                                                                              |
| `.restartChat`     | `"restart-chat"`     | User restarted the session     | —                                                                              |
| `.downloadChat`    | `"download-chat"`    | User downloaded the transcript | —                                                                              |
| `.deleteChat`      | `"delete-chat"`      | Chat history cleared           | —                                                                              |
| `.changeLanguage`  | `"change-language"`  | Language switched              | `languageCode`, `languageName`, `previousLanguageCode`, `previousLanguageName` |
| `.showFeedback`    | `"show-feedback"`    | Feedback prompt shown          | `feedbackType`                                                                 |
| `.submitFeedback`  | `"submit-feedback"`  | Feedback submitted             | `feedbackType`, `feedbackValue`                                                |
| `.dismissFeedback` | `"dismiss-feedback"` | Feedback dismissed             | `feedbackType`                                                                 |

***

## Worked Examples

### React to a bot reply only

```swift
KindlySDK.events
    .compactMap { event -> KindlyEventMessage? in
        guard case .message(let new, _) = event.detail,
              new.sender == .bot else { return nil }
        return new
    }
    .sink { botMessage in
        print("Bot: \(botMessage.text ?? "")")
    }
    .store(in: &cancellables)
```

### Log every button the user taps

```swift
KindlySDK.events
    .sink { event in
        if case .buttonClick(let button, let type, _, _) = event.detail {
            print("User tapped [\(type)] \(button.label) → value=\(button.value ?? "nil")")
        }
    }
    .store(in: &cancellables)
```

### Track language switches

```swift
KindlySDK.events
    .sink { event in
        guard case .ui(let action) = event.detail,
              action.type == .changeLanguage,
              let to = action.languageCode,
              let from = action.previousLanguageCode else { return }
        analytics.log("language_changed", from: from, to: to)
    }
    .store(in: &cancellables)
```

### Wait for connection, then trigger a dialogue

```swift
KindlySDK.state
    .filter { $0.isConnected }
    .prefix(1)
    .sink { _ in
        KindlySDK.triggerDialogue(id: "welcome_flow")
    }
    .store(in: &cancellables)
```

### Collect the full chat transcript on demand

```swift
KindlySDK.events
    .compactMap { event -> [KindlyEventMessage]? in
        if case .message(_, let log) = event.detail { return log }
        return nil
    }
    .sink { transcript in
        // transcript is always the full, up-to-date chat log
        currentTranscript = transcript
    }
    .store(in: &cancellables)
```

***

## Threading Notes

- Both publishers emit on the thread the SDK processes the event from.
- For UI work, dispatch to the main queue explicitly: `.receive(on: DispatchQueue.main)`.

```swift
KindlySDK.state
    .receive(on: DispatchQueue.main)
    .sink { state in
        self.statusLabel.text = state.connectionState
    }
    .store(in: &cancellables)
```
