https://github.com/Pariette-Inc/signalbird.sdkSignalbird - Swift (iOS, macOS)
It carries two clients:
| Class | What for | Key | Where |
|---|---|---|---|
SignalbirdApp |
live chat + push registration for the end user | sb_public_live_… (public) |
the iOS app |
SignalbirdClient |
Radio - writing logs and events | sb_secret_live_… (secret) |
server-side Swift (Vapor, Hummingbird) |
A secret key is never embedded in an iOS app: the app bundle can be unpacked and the key ends up in everyone's hands. The mobile side does chat and push registration with the public key.
Install
Swift Package Manager - Xcode → File → Add Package Dependencies:
https://github.com/Pariette-Inc/signalbird.sdkor Package.swift:
dependencies: [
.package(url: "https://github.com/Pariette-Inc/signalbird.sdk", from: "1.2.0"),
],
targets: [
.target(name: "App", dependencies: [.product(name: "Signalbird", package: "signalbird.sdk")]),
]iOS 15+, macOS 12+. It has no dependencies: Foundation/URLSession only.
Live chat
import Signalbird
let client = try SignalbirdApp(config: .init(
publicKey: "sb_public_live_…",
chatKey: "destek",
pushKey: "mobil"
))chatKey is the chat channel's module key and pushKey the push channel's (the channel names you create in the panel; not secret). The SDK sends pushKey on device and push endpoints (registerDevice, unregisterDevice, reportPushOpened) and chatKey on chat endpoints, as the X-Signalbird-Module-Key header; if the relevant key is missing the API returns 400 MODULE_KEY_MISSING (2.10.0).
You get the app key from the panel (Inbox → Settings → Apps) or by calling
createApp with the Management SDK.
Bootstrap
let boot = try await client.bootstrap()
guard let app = boot.dictionary?["app"] as? [String: Any],
app["chat_enabled"] as? Bool == true else {
return // chat is off for this app: draw nothing
}bootstrap also returns colours, the greeting text, working hours and
pre-chat form settings; read them from there if you want your interface to be
manageable from the panel.
Session and conversation
// The visitor session: the secret is stored, so history survives the next launch
try await client.startSession(["name": "Ayşe", "email": "ayse@example.com"])
// The first message opens the conversation. The quota is spent here: per conversation.
let started = try await client.startConversation(body: "Where is my parcel?")
let conversationID = (started.dictionary?["conversation"] as? [String: Any])?["id"] as? String ?? ""
try await client.sendMessage(conversationID: conversationID, body: "Order number: 1234")Fetching new messages
// The `after` cursor: only messages newer than this one come back
let updates = try await client.getConversation(conversationID, after: lastMessageID)There is no WebSocket. Poll every 3 seconds while the panel is open and on a gradually widening interval when it is closed (20 → 60 → 180 s); skip a round while the app is in the background. This recovers by itself when the connection drops and does not burn battery.
Interactions
try await client.setTyping(conversationID: conversationID, isTyping: true)
try await client.markRead(conversationID: conversationID, lastMessageID: lastID)
try await client.reactToMessage(conversationID: conversationID, messageID: messageID, emoji: "👍")
// Only their own message, and only within 15 minutes of sending it
try await client.editMessage(conversationID: conversationID, messageID: messageID, body: "Correction")
try await client.deleteMessage(conversationID: conversationID, messageID: messageID)
try await client.rateConversation(conversationID, rating: 5, comment: "Thank you")
try await client.closeConversation(conversationID)Identity
// When the user signs in, bind the visitor to the contact record
try await client.identify([
"external_id": user.id,
"email": user.email,
"name": user.displayName,
"attributes": ["plan": "pro"],
])
// On sign-out, clear the local identity; the record on the server stays
client.signOut()Push (APNs)
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%02x", $0) }.joined()
Task {
try? await client.registerDevice(
token: token,
externalID: currentUser?.id,
deviceName: UIDevice.current.name,
appVersion: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
)
}
}Asking for notification permission and obtaining the token is the app's job: when you ask for that permission is a product decision, and the App Store takes it seriously. The SDK only forwards the token.
On sign-out:
try await client.unregisterDevice(token: token) // the record is deactivated, not deletedStoring the visitor secret
The default is UserDefaults. If you want Keychain, supply your own adapter:
struct KeychainStorage: SignalbirdStorage {
func get(_ key: String) -> String? { /* … */ }
func set(_ key: String, _ value: String) { /* … */ }
func remove(_ key: String) { /* … */ }
}
let client = try SignalbirdApp(config: .init(
publicKey: "sb_public_live_…",
chatKey: "destek",
pushKey: "mobil",
locale: "en",
storage: KeychainStorage()
))The stored record carries the app key too: if you rotate the key, the old identity is discarded automatically and a new session opens. Otherwise every call made with the old secret would take a 401 and the chat would die silently.
Radio (server-side Swift)
let radio = try SignalbirdClient(config: .init(
domainKey: ProcessInfo.processInfo.environment["SIGNALBIRD_DOMAIN_KEY"] ?? "",
source: "api-01"
))
try await radio.critical("critical", "the payment service is not responding", [
"service": "iyzico",
])
try await radio.batch([
["channel": "deploy", "message": "v2.4.0 is live", "level": "info"],
])Five levels: .debug, .info, .warn, .error, .critical.
Result shape
public struct SbResult {
public let ok: Bool
public let status: Int
public let data: Any?
public let code: String?
public let message: String?
public var dictionary: [String: Any]? { get }
public var array: [[String: Any]]? { get }
public func decode<T: Decodable>(_ type: T.Type) -> T?
}To decode into your own model:
struct ConversationResponse: Decodable {
struct Conversation: Decodable { let id: String; let status: String }
let conversation: Conversation
}
let parsed = updates.decode(ConversationResponse.self)Version notice
The SDK reports its version on every request: X-Signalbird-Sdk: swift/<version>.
When a newer version is released the API responds with
Signalbird-Sdk-Status: outdated (or unsupported once it is no longer
supported) and the SDK writes a warning once per process (print, DEBUG builds only).
The warning is not an error: the request result does not change.
In the panel, Domains → [domain] → Keys shows which SDK version each key is used with; keys on an old version also get a banner at the top of the panel and a notification. To update, use File → Packages → Update to Latest Package Versions in Xcode.
Error codes
| Code | Meaning |
|---|---|
NO_KEY / WRONG_KEY_TYPE |
The initialiser throws: a key from the wrong family |
VISITOR_INVALID |
The secret is invalid; the local identity is cleared and the next call opens a new session |
CHAT_UNAVAILABLE |
The quota is full or chat is off |
MODULE_DISABLED |
Your package does not include the chat or push module |
NETWORK_ERROR / TIMEOUT |
Network (status 0) |
Apart from the key check in the initialiser, no method throws; the result is always the envelope.