Skip to content

Swift

Identify your users and track churn-signal events from iOS, macOS, tvOS, and watchOS apps. An actor-based client with an ordered durable queue, retry handling, and stable event idempotency.

Add the package in Xcode (File → Add Package Dependencies…) or in Package.swift:

.package(url: "https://github.com/WhisperrAI/whisperr-swift.git", from: "0.1.0")

Published on the Swift Package Index as WhisperrAI/whisperr-swift.

Call once at startup. Get an app ingestion key from the Whisperr dashboard → Developer → API Keys.

import Whisperr
await Whisperr.initialize(apiKey: "wrk_xxx")

Or construct a client directly for explicit lifetimes and dependency injection:

let whisperr = WhisperrClient(apiKey: "wrk_xxx")

Set who the current user is. Idempotent and safe to call on every login. Traits are merged server-side; channels are how Whisperr can reach the user — and whether it’s allowed to.

// Common case — email/phone/pushToken expand into opted-in channels:
try await whisperr.identify(
"user_123",
traits: ["name": "Ada", "plan": "pro"],
email: "ada@example.com",
phone: "+15551234567"
)
// Full control — consent and verification:
try await whisperr.identify(
"user_123",
channels: [
.email("ada@example.com", verified: true),
.sms("+15551234567", optedIn: false) // opted out of SMS
]
)

The SDK never bundles a push library — hand it the APNs device token (or an FCM registration token string) and Whisperr keeps the push channel current. setPushToken is safe to call on every launch: repeated tokens are a no-op, a rotated token opts the previous one out, and a token set before login buffers and attaches to the next identify().

func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
Task {
// Hex-encodes the token and forwards it to setPushToken(_:).
try await whisperr.setPushToken(deviceToken: deviceToken)
}
}

After reset() (logout), call setPushToken again once the next user logs in.

Record product events. Buffered and sent in batches; the timestamp is captured at call time, so events recorded offline keep their real time.

try await whisperr.track("checkout_completed", properties: ["amount": 42, "currency": "USD"])

track() uses the user from the most recent identify() — it throws WhisperrClientError.missingUserID if no user is known. Pass userID: explicitly for events emitted outside a signed-in session:

try await whisperr.track("trial_expired", userID: "user_123")

Event names must be snake_case — see Event design.

await whisperr.reset() // flushes, then clears the current user
  • Durable queueidentify and track append to an ordered queue and deliver in order; track calls coalesce into /v1/events/batch.
  • Offline — the queue is persisted (via UserDefaults by default) and survives app restarts. Transient failures retry with exponential backoff, auth errors pause delivery and keep the queue, permanent client errors drop the offending item — the standard delivery contract.
  • Each event carries a stable $message_id, so at-least-once retries dedup server-side.
let whisperr = WhisperrClient(
apiKey: "wrk_xxx",
options: WhisperrOptions(
flushInterval: 15, // seconds
flushAt: 20,
maxBatchSize: 500, // backend hard cap
maxQueueSize: 1_000, // drops oldest beyond this
enablePersistence: true,
debug: false,
onError: { error in
print("whisperr:", error.type.rawValue, error.message)
}
)
)
await whisperr.flush() // force delivery (e.g. before a critical await)

Pass InMemoryWhisperrPersistence() for tests or ephemeral runtimes, or your own WhisperrPersistence for custom storage.

The ingestion key is embedded in your app, like a Segment write key or Amplitude API key. It can only ingest events for your app; treat it as publishable, not secret.