Documentation
SDKs

Android

Use the FounderHQ Android SDK.

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

Before you start

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

Install

dependencies {
    implementation("com.getfounderhq:events:0.8.0")
    // Navigation Compose screens
    implementation("com.getfounderhq:events-compose:0.8.0")
}

The SDK needs minSdk 24 and Java 17.

Initialize

Create one client in your Application, and share it.

class MyApplication : Application() {
    lateinit var events: FounderHQEvents

    override fun onCreate() {
        super.onCreate()
        events = FounderHQEvents(this, "fhq_pk_XXXX")
    }
}

Pass a config when you want to change the defaults:

events = FounderHQEvents(
    this,
    "fhq_pk_XXXX",
    FounderHQEventsConfig(captureScreens = true),
)

The client registers activity lifecycle callbacks, captures activity screens and application lifecycle, and records the session start.

Capture events

events.capture("trial.started", mapOf("plan" to "growth"))

capture waits for startup to finish, then queues the event. Call readyForCapture() when you want to wait for startup on its own.

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. After you read Play Install Referrer data, pass it on:

events.captureInstallReferrer(mapOf("utm_source" to "google_play"))

Track screens

Activity screens are captured for you. Call events.screen("Pricing") anywhere else.

With Navigation Compose, add the observer inside your NavHost scope:

import com.founderhq.events.compose.FounderHQNavigationObserver

FounderHQNavigationObserver(navController = navController, events = events)

It records the current route, and skips a repeat of the route the app is already on.

Identify a contact

events.identify("user_42", mapOf("email" to "jane@acme.com"))

Change identity and account in one call:

events.identify(
    "user_42",
    mapOf("email" to "jane@acme.com"),
    FounderHQAccountContext("workspace_123"),
)

Update contact properties on their own with setPersonProperties(set, setOnce). With the default IDENTIFIED_ONLY 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", mapOf("plan" to "growth"))
events.setAccountProperties(mapOf("seats" to 12))
events.clearAccount() // resetAccounts() does the same

setAccount also takes a FounderHQAccountContext, which carries key, properties, and contextToken. Set account in the config 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, and the queue is cleared
isOptedOut()Returns true while capture is off

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

The SDK never collects advertising identifiers.

Purchases

The client can tie a Play Billing 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, completion)Records intent before checkout, and returns the purchase context
applyPurchaseContext(builder, prepared)Puts the context on your Play Billing flow
observePurchase(purchase, prepared)Reports the purchase that followed
claimSubscription(purchase, confirmation)Moves future revenue of an existing subscription

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

Config

FounderHQEventsConfig takes these values.

ValueTypeDefaultWhat it does
hostStringhttps://i.getfounderhq.comWhere events are sent
flushAtInt20Sends a batch once this many events are queued
flushIntervalSecondsLong10Sends a batch on this timer. 0 turns the timer off
personProfilesPersonProfilesIDENTIFIED_ONLYWhen contact property changes are applied
optOutByDefaultBooleanfalseStarts opted out
captureLifecycleBooleantrueApp opened and backgrounded events
captureScreensBooleantrueActivity screen events
captureSessionsBooleantrueSession start events
captureInstallUpdatesBooleantrueApp installed and updated events
remoteConfigBooleantrueReads capture settings you set in FounderHQ
accountFounderHQAccountContext?nullInstalls account context before the first event
purchasePrepareTimeoutMillisLong3000How long preparePurchase waits before checkout goes ahead offline
captureElementInteractionsBooleanfalseTaps and rage taps. See Element interactions
capturePushNotificationOpenedBooleantrueRecords the notification taps your app reports
tracingHeadersList<String>?nullHostnames whose requests carry the session id
maxQueueSizeInt1000Events kept offline before the oldest are dropped
eventTtlMillisLong86400000How long an unsent event may wait
maxRetriesInt5Times a failed event is retried
debugBooleanfalseLogs what the SDK drops or refuses

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

Sending

MethodReturnsWhat it does
flush()BooleanSends queued events now, and tells you whether the queue drained
close()Flushes, stops the executors, and unregisters the lifecycle callbacks
getDistinctId()StringThe current identity
getSessionId()StringThe current session

FounderHQEvents implements AutoCloseable.

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 view class, resource entry name, and content descriptionAnything a person typed
The path through the view hierarchyTap coordinates
The label a Button shows, or the selected tab of a TabLayoutThe contents or hint of an EditText

A tap on an EditText records nothing at all, and a plain TextView never contributes its words. Give a view the tag android:tag="fhq-no-capture" to skip it and everything inside it.

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

Only a tap counts. A finger that moves further than the platform's touch slop, a second finger, or a cancelled touch means the gesture was a scroll. Lifting a finger over a row at the end of a scroll records nothing.

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 config 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(remote) to apply settings you supply yourself. Set remoteConfig = false in the config 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