Kindly iOS SDK — Events Reference
Deep reference for KindlyEvent and every payload type in the iOS SDK event system. Companion to 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:
KindlySDK.events: AnyPublisher<KindlyEvent, Never> // all events
KindlySDK.state: AnyPublisher<KindlyEventState, Never> // state-only convenienceTypical subscription:
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
public struct KindlyEvent {
public let type: KindlyEventType
public let detail: KindlyEventDetail
public let timestamp: Date // when the event was emitted
}KindlyEventDetail
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
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
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
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
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
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
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
public struct KindlyEventImage {
public let url: String
public let altText: String?
public let id: String?
}KindlyEventAttachment
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
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
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
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
KindlySDK.state
.filter { $0.isConnected }
.prefix(1)
.sink { _ in
KindlySDK.triggerDialogue(id: "welcome_flow")
}
.store(in: &cancellables)Collect the full chat transcript on demand
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).
KindlySDK.state
.receive(on: DispatchQueue.main)
.sink { state in
self.statusLabel.text = state.connectionState
}
.store(in: &cancellables)