iOS SDK
Events and chat visibility
Use ReceptionKit events to record chat interactions in your app, and observe chat visibility to adapt your own interface. Events report specific SDK actions; they do not prove that a person read a message, received a notification, or submitted an App Store review.
Register an event handler
Reception.shared.onEvent is an optional, synchronous callback with this declaration on the @MainActor-isolated Reception class:
public var onEvent: ((ReceptionEvent) -> Void)?Its default is nil. Assign it on the main actor during app setup, before presenting chat or starting an operation you want to observe. There is one handler for the SDK, not one per view or conversation. Assigning another closure replaces the previous handler. Events are not buffered or replayed when you install a handler later.
SDK event call sites invoke the handler on the main actor. Keep it short so it does not delay chat updates. Forward events to your app’s existing analytics handler; perform network work asynchronously. If several features need the events, distribute them from one app-owned handler.
Exhaustive switch example
Place this function in your app’s integration code and call it after configuration. Supply your existing event handler as record; the example passes event names only.
import ReceptionKit
@MainActor
func installReceptionEventHandler(record: @escaping (String) -> Void) {
Reception.shared.onEvent = { event in
let name: String
switch event {
case .chatOpened:
name = "support_chat_opened"
case .messageSent:
name = "support_message_confirmed"
case .imageSent:
name = "support_message_with_images_confirmed"
case .reviewOpened:
name = "support_review_link_tapped"
case .paywallOpened:
name = "support_paywall_opened"
case .dataDeleted:
name = "support_delete_data_completed"
case .identityRejected:
name = "support_identity_token_rejected"
}
record(name)
}
}The switch intentionally covers all seven cases. Review it when updating ReceptionKit if the compiler reports a new case. Only identityRejected carries a value, a ReceptionError with the rejection code; the callback never contains a message ID, text, image count, review URL, user ID, or token.
Remove or replace the handler
Place this function in the same app-owned integration code and call it on the main actor when you want to stop observing events:
@MainActor
func removeReceptionEventHandler() {
Reception.shared.onEvent = nil
}Configuration changes and Reception.shared.logout() do not automatically clear this handler. If it captures an account-specific object, update it as part of your account lifecycle. A captured object remains retained by the closure unless you use a weak capture or remove the handler. See Login and logout.
Event meanings
chatOpened
Fires when chat content becomes visible, including the SDK sheet and a host-presented ReceptionChatView.
Calling Reception.shared.openChat() starts presentation; it does not itself emit this event. The event can fire again when chat content reappears or is recreated, so do not treat it as a once-per-user or once-per-session signal.
It does not prove that messages loaded, a device registered, a conversation exists, or a network connection succeeded. An unused empty chat can appear without registering a device.
messageSent
Fires when ReceptionKit confirms a message was sent for the current session. It covers text messages, image-only messages, and messages containing both text and images. A successful retry can produce it after an earlier failure.
It does not fire merely because the person tapped Send, because a pending bubble appeared, or because an upload finished. Failed requests do not emit it. A request can reach the server while its response is lost, so an absent callback alone does not prove the server lacks the message.
This confirms the SDK processed a successful send response, not that an agent read the message or that Telegram delivered a notification. See Chat features for sending and retry behavior.
imageSent
Fires immediately after messageSent when the confirmed outgoing draft contains images. One successful send containing several images emits one imageSent, not one event per image.
Selecting photos, starting uploads, and viewing an image do not emit it. It does not prove an agent viewed or downloaded the images. To count all confirmed sends, count messageSent alone; adding imageSent would count sends with images twice.
reviewOpened
Fires when the person activates a review card's App Store action. The SDK reports the tap and asks the system to open the review URL.
It does not wait for URL-opening success or for the server to acknowledge the click receipt. Repeated taps can emit repeated callbacks. Neither this event nor the dashboard's click receipt proves a review was submitted, published, or given a particular rating. See Review requests.
paywallOpened
Fires when the customer activates a paywall card. Your Reception.shared.onPaywall callback routes the id to your purchase screen. This records an open, not a successful presentation or purchase. See Paywalls.
dataDeleted
Fires when Reception.shared.deleteData() reaches its successful completion path, after the local reset step. It also fires when the configured device has no session and therefore sends no deletion request, and when Reception no longer knows the session or the app, which the SDK treats as completion. Before configuration, deleteData() does nothing and emits nothing.
If configuration or session state changed while the deletion request was in flight, the SDK protects the newer session from being reset; the completed call still emits the event. Other request failures throw and do not emit dataDeleted.
This is a completion signal for that SDK call, not proof that the server deleted records in every case or that data on other devices was deleted. Reception.shared.logout() and automatic handling of a server reset do not emit it. When an account is deleted, await Reception.shared.deleteData() instead of calling logout, and handle failures at the call site so you can retry. See Delete support data.
identityRejected
Fires when Reception rejects the identity token staged with Reception.shared.identify(token:). The associated ReceptionError has the code identity_token_invalid, identity_token_expired, or identity_not_configured and its HTTP status. ReceptionKit has dropped the token. Existing verification is unchanged; unverified chats remain subject to the app’s verified-only setting. A rejected registration leaves the send ready to retry.
Fetch a fresh token from your server for identity_token_expired. The other codes point to a problem on your server or in the dashboard, so retrying with a new token of the same kind does not help. The SDK ignores ids_ secrets, tokens over 4,096 characters, and tokens with an unreadable user_id. These inputs emit no event. Other invalid tokens can reach Reception and emit identityRejected. See Identity verification.
Observe isChatOpen
Reception.shared.isChatOpen is a read-only, main-actor-isolated Bool exposed through the observable Reception singleton. It starts as false, becomes true when chat appears, and returns to false when chat disappears or the support session resets.
Read it directly in SwiftUI to update host-owned UI. Place this example view in your app and mount it in an existing screen to inspect visibility while integrating:
import SwiftUI
import ReceptionKit
@MainActor
struct SupportVisibilityStatus: View {
var body: some View {
Text(Reception.shared.isChatOpen ? "Chat visible" : "Chat not visible")
}
}The observer must remain mounted to receive changes. You do not need to copy the value into separate @State or install an event handler to read it. For a sheet you present yourself, you can also use your own presentation binding and dismissal callback; see Entry point and presentation.
Visibility is not a connection indicator, foreground-state indicator, or proof that the person is reading. Moving the app to the background pauses live chat updates without necessarily making the content disappear, so isChatOpen can remain true. Calling Reception.shared.openChat() does not synchronously make it true. If your UI needs foreground state too, observe SwiftUI's scenePhase separately.
One practical use is suppressing a foreground support notification banner while chat is visible. Read the property on the main actor in your existing notification integration; see Push notifications. Observe Reception.shared.unreadCount separately for badges, as described in Unread messages.
API boundaries
There is no chatClosed, incoming-message, send-failure, read-receipt, or connection-state case in ReceptionEvent. Observe visibility or your own presentation state for dismissal-related behavior. A transition to false does not identify why the chat stopped being visible.
There is no public SDK send method. The SDK's composer owns sending; registering a callback observes that behavior. Reception.shared.closeChat() dismisses a sheet presented by the SDK, while your app dismisses a host-owned ReceptionChatView. There is no public Reception.shared.dismiss() method. The SDK reference lists the public API.
Verify your integration
Use a development project and install the example handler before opening chat:
Open chat and check for
support_chat_opened. Dismiss it and check that visibility becomes false; no close event is expected.Send a text message through the composer. After confirmation, expect
support_message_confirmed.Send one message containing multiple images. Expect
support_message_confirmedfollowed by onesupport_message_with_images_confirmed.With a real review card in the development conversation, tap its action and check for
support_review_link_tapped. Check URL opening separately.When deliberately testing deletion of that development device's data, await
Reception.shared.deleteData()and check forsupport_delete_data_completedon completion. Verify server removal separately if a registered device existed.To test identity verification, start with a fresh, unregistered support session. Pass a token signed with the wrong secret and send a message. Expect
support_identity_token_rejectedand a send available to retry. If unverified chat is allowed, retrying without the token can send it unverified. On registered devices, the SDK can retry the update without the token and send successfully.
If callbacks are missing, check whether another integration replaced Reception.shared.onEvent, whether the operation succeeded, and whether the handler was installed before the event. Send failures and discarded responses from an old session do not produce a confirmed-send callback. For presentation and connection problems, see Troubleshooting.