iOS SDK

Track product usage, screen views, and user identity from iOS, macOS, tvOS, and watchOS apps using the official Ripples Swift SDK.

Client-side tracking. Use the iOS SDK to send events directly from the device — pageviews (screen views), product usage, signups, and identify. Revenue should be tracked server-side via a payment integration or a server SDK.

Install

Add the package via Swift Package Manager:

.package(url: "https://github.com/ripplesanalytics/ripples-ios", from: "0.1.6")

Or in Xcode: File → Add Package Dependencies and paste https://github.com/ripplesanalytics/ripples-ios.

Keys

Ripples projects have two identifiers. The iOS SDK takes the project token, never the secret key.

Key Format Where to use Scope
Secret key priv_… Server-side only Full ingest access, including revenue
Project token UUID iOS / web / any client track, identify, signup, pageview only

The project token is safe to bundle in a mobile or web app — revenue events submitted with the token are rejected server-side, so a scraped key can't forge MRR or LTV. Rotate it in project settings if you see abuse.

Never ship the priv_ key in a mobile or web app.

Quick start

Initialize once at app launch:

import Ripples

@main
struct MyApp: App {
    init() {
        Ripples.setup(RipplesConfig(projectToken: "YOUR-PROJECT-TOKEN"))
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

Identify users

Call identify to associate the visitor with your internal user ID and update traits. Traits are cached and forwarded with every subsequent event automatically.

Ripples.shared.identify("user_123", traits: [
    "email": "[email protected]",
    "name":  "Jane Smith",
])

Parameters

Parameter Type Description
userId required String Your internal user ID.
traits optional [String: Any] User traits: email, name, avatar_url, plus any custom keys (plan, company, role, …).

Track product usage

Call track only for significant product usage — actions that prove a user got real value (created a budget, sent a message, invited a teammate). This is not a generic event log like PostHog or Mixpanel: do not send screen views, banner impressions, button taps, or "viewed X" events. Every track call feeds the Activation dashboard, so noise here pollutes your funnel. (Use screen(_:) / trackScreen for navigation — see below.)

// Group actions by product area
Ripples.shared.track("created a budget", area: "budgets")

// Mark THIS occurrence as the activation moment
Ripples.shared.track("added transaction", area: "transactions", properties: [
    "activated": true,
])

// area can also be passed inside properties — both forms are equivalent
Ripples.shared.track("exported report", properties: ["area": "reports"])

How it works. Ripples auto-detects activation moments (first occurrence per user per action), computes adoption rates by product area, and correlates usage patterns with retention and payment.

Keeping user data fresh

Pass userProperties alongside any event to upsert the user record without a separate identify call. This mirrors how PostHog handles $set — one request carries both the event and the trait update:

Ripples.shared.track("created a budget",
                     area: "budgets",
                     userProperties: ["plan": "pro", "company": "Acme"])

Traits from the last identify call are cached and forwarded automatically with every subsequent event, so you generally don't need to pass userProperties explicitly. Pass an empty dictionary [:] to suppress forwarding for a specific call.

Parameters

Parameter Type Description
actionName required String What the user did. Be specific: "created a budget", not "budgets".
area optional String Product area this action belongs to (e.g. "budgets", "reports"). Groups actions in the dashboard.
properties optional [String: Any] Extra event properties. Pass "activated": true on the specific occurrence when activation happens for this user.
userProperties optional [String: Any] User trait upsert sent with this event. Pass [:] to suppress the cached-traits forwarding for this call.

Track screen views

Screen views are stored as pageview events and appear in the Pages report alongside web pageviews. The first screen in each session is automatically flagged as the session entry.

Use the SwiftUI modifier — one line per screen:

struct HomeView: View {
    var body: some View {
        List { ... }
            .trackScreen("Home")
    }
}

// With area and extra properties
struct ListDetailView: View {
    let listId: String
    var body: some View {
        ScrollView { ... }
            .trackScreen("ListDetail", properties: ["area": "lists", "list_id": listId])
    }
}

Or call it imperatively (UIKit, custom navigation):

Ripples.shared.screen("Settings")
Ripples.shared.screen("ListDetail", area: "lists", properties: ["list_id": listId])

Flush manually

Events are batched in memory, persisted to disk, and flushed automatically on a timer, when the queue fills, and when the app enters background or terminates. Call flush explicitly before logout, account deletion, or any moment you want delivery to be attempted immediately:

Ripples.shared.flush { /* delivery attempted */ }

Automatic behaviour

What How
Persistent visitor ID Generated once, stored to disk, survives reinstalls.
Session ID New UUID on SDK init; rotates after 30 min in background.
Device & OS metadata Collected once at startup, merged into every event automatically.
Geo / country Resolved server-side from the request IP via Cloudflare headers.
Offline queuing Events persisted to disk; flushed when connectivity returns.
Background flush Queue flushed on didEnterBackground and willTerminate.
Retry / backoff 5xx and network errors back off exponentially (5s → 5 min).
Poison batch protection Non-retryable 4xx drops the batch so a bad payload can't wedge the queue.

Configuration

let config = RipplesConfig(projectToken: "YOUR-PROJECT-TOKEN")
config.host                 = "https://your-domain.com"  // self-hosted
config.flushIntervalSeconds = 30
config.flushAt              = 20
config.maxBatchSize         = 50
config.maxQueueSize         = 1000
config.requestTimeout       = 10
config.onError              = { error in print("Ripples error:", error) }
Ripples.setup(config)

Configuration options

Option Description
host API endpoint. Override for self-hosted deployments.
flushIntervalSeconds How often the timer attempts to flush the queue.
flushAt Auto-flush as soon as the queue reaches this many events.
maxBatchSize Maximum events sent in a single HTTP request.
maxQueueSize Hard cap on the on-disk queue. Oldest events are dropped past this.
requestTimeout HTTP request timeout in seconds.
onError Closure invoked on delivery failure. Wire this to your logging or Sentry.

Requirements

  • Swift 5.5+
  • iOS 13+, macOS 10.15+, tvOS 13+, watchOS 6+