Documentation
SDKs

iOS

Use the FounderHQ iOS SDK.

Capture screens, app lifecycle, and your own events from a Swift app.

Before you start

You need a publishable key (fhq_pk_...) for the brand you want to measure. See Getting started.

Install

In Xcode, add https://github.com/FounderHQ/founderhq-events-ios with Swift Package Manager and select version 0.8.0 or later.

For CocoaPods:

pod 'FounderHQEvents', '~> 0.8.0'

You can also install directly from the Git release:

pod 'FounderHQEvents', :git => 'https://github.com/FounderHQ/founderhq-events-ios.git', :tag => 'v0.8.0'

The SDK needs iOS 15 or macOS 12, and Swift 5.9.

Initialize

Create one client and keep it for the life of the app.

import FounderHQEvents

let events = FounderHQEvents(apiKey: "fhq_pk_XXXX")

Pass a configuration when you want to change the defaults:

let events = FounderHQEvents(
    apiKey: "fhq_pk_XXXX",
    configuration: .init(
        personProfiles: .identifiedOnly,
        captureScreens: true
    )
)

The client captures UIKit screens and application lifecycle on its own, and records the session start.

Capture events

events.capture("trial.started", properties: ["plan": "growth"])

capture returns at once and does the work in the background. Every writing method has an ...AndWait twin that you can await: captureAndWait, identifyAndWait, screenAndWait, setAccountAndWait, clearAccountAndWait, setAccountPropertiesAndWait, setPersonPropertiesAndWait, registerAndWait, registerOnceAndWait, unregisterAndWait, optInAndWait, optOutAndWait, and resetAndWait. readyForCapture() resolves once startup and the queued calls are done.

Record a deep link when your app opens from one:

events.captureDeepLink(url)

captureDeepLink drops the query and fragment, and keeps the campaign parameters it recognizes.

Track screens

UIKit screens are captured for you. In SwiftUI, mark the view:

PricingView()
    .founderHQScreen("Pricing", client: events)

Call events.screen("Pricing") directly anywhere else.

Identify a contact

events.identify("user_42", properties: ["email": "jane@acme.com"])

Change identity and account in one call:

events.identify(
    "user_42",
    properties: ["email": "jane@acme.com"],
    account: FounderHQAccountContext(key: "workspace_123")
)

Update contact properties on their own with setPersonProperties(_:setOnce:). With the default identifiedOnly mode, changes made before identify stay on the device and go out with the identify call.

Call reset() on logout. It clears the identity and the account.

Accounts

events.setAccount("workspace_123", properties: ["plan": "growth"])
events.setAccountProperties(["seats": 12])
events.clearAccount() // resetAccounts() does the same

setAccount also takes a FounderHQAccountContext, which carries key, properties, and contextToken. Set account in the configuration when the first automatic event must already carry it. An account change rotates the account span only. The person session and queued events are untouched. See Accounts and groups.

You callWhat happens
optIn()Capture is on
optOut()Capture stops
isOptedOut()Returns true while capture is off

Set optOutByDefault: true in the configuration to start silent until the person agrees. See Consent and privacy.

The SDK never collects advertising identifiers.

Purchases

The client can tie a StoreKit or RevenueCat purchase to the person who made it.

MethodWhat it does
purchaseAttribution()Returns the identifiers to pass to the store or to RevenueCat
preparePurchase(source:)Records intent before checkout, and returns the purchase context
purchase(_:options:)Runs a StoreKit purchase with the context already attached
observePurchase(_:prepared:)Reports the purchase that followed
claimSubscription(_:confirmation:)Moves future revenue of an existing subscription

The full flow, including what each store needs, is on Mobile purchase claims.

Configuration

FounderHQEventsConfiguration takes these values.

ValueTypeDefaultWhat it does
hostURLhttps://i.getfounderhq.comWhere events are sent
flushAtInt20Sends a batch once this many events are queued
flushIntervalTimeInterval10Sends a batch on this timer, in seconds
personProfilesFounderHQPersonProfiles.identifiedOnlyWhen contact property changes are applied
optOutByDefaultBoolfalseStarts opted out
captureLifecycleBooltrueApp opened and backgrounded events
captureScreensBooltrueUIKit screen events
captureSessionsBooltrueSession start events
captureInstallUpdatesBooltrueApp installed and updated events
remoteConfigBooltrueReads capture settings you set in FounderHQ
accountFounderHQAccountContext?nilInstalls account context before the first event
purchasePrepareTimeoutTimeInterval3How long preparePurchase waits before checkout goes ahead offline
captureElementInteractionsBoolfalseTaps and rage taps. See Element interactions
capturePushNotificationOpenedBooltrueRecords when someone taps one of your notifications
tracingHeaders[String]?nilHostnames whose requests carry the session id
maxQueueSizeInt1000Events kept offline before the oldest are dropped
eventTTLTimeInterval86400How long an unsent event may wait, in seconds
maxRetriesInt5Times a failed event is retried
debugBoolfalseLogs what the SDK drops or refuses

FounderHQEventsDependencies replaces the clock, UUID source, storage, transport, platform facts, and screen-capture installer. Use it in tests.

Sending

MethodReturnsWhat it does
flush()BoolSends queued events now, and tells you whether the queue drained
close()Flushes, stops the timer, and removes the lifecycle observers
getDistinctId()StringThe current identity
getSessionId()StringThe current session

Element interactions

Set captureElementInteractions to true. The SDK then records $autocapture when someone taps a control, and $rageclick when they tap the same control three times in a second. Both are off by default.

Each event carries the control and up to four of its parents.

StoredNot stored
The class, accessibility identifier, and accessibility labelAnything a person typed
The action, the enabled state, and the selected stateTap coordinates
The title a UIButton, UIBarButtonItem, or UISegmentedControl showsThe contents of a UITextField, UITextView, or UISearchBar

Text fields, text views, and search bars are skipped. A tap on one records nothing at all.

Titles are cut to 255 characters. Numbers that look like a card number or a social security number are removed from them.

Remote config

The client reads your capture settings from FounderHQ at startup, caches them in its storage, and applies the cached copy on the next launch.

KeyWhat it turns on and off
capture_sessionsSession start events
capture_screensScreen events
capture_lifecycleApp opened and backgrounded events
autocaptureTaps and rage taps
capture_rageclicksRage taps only. Taps are still recorded

Your settings in FounderHQ win. They can turn capture off for every install, and on for an app that shipped with the wrong value. You cannot rebuild an app people have already installed, so the dashboard decides. The values in your configuration apply until your settings arrive.

Events already queued under a key that turns off are dropped before they leave the device.

Call refreshRemoteConfig() to fetch them again while the app runs. Call applyRemoteConfig(_:) to apply settings you supply yourself. Set remoteConfig: false in the configuration to skip the request.

Next

Read the protocol reference for the wire format, the reserved event names, and the campaign properties a deep link can carry.

AI agent or LLM? Read this page as markdown

On this page