Events
The full event vocabulary: automatic, fixed manual, and custom events, plus the context stamped on every one.
Other guides: quickstart.md · revenuecat.md · agent-api.md · apps.md · apple-ads.md
Automatic — the SDK sends these; never call them yourself
| Event | When | Dashboard use |
|---|---|---|
first_open |
Once per install, at first configure |
Installs headline; the install cohort every funnel stage is measured against; pricing-tier splits; install bars |
app_open |
Every new session: launch, or foreground after 30+ min in background | "Online now", activity chart, app-opens in the agent API |
Session length is measured from the session_id the SDK stamps on every event: the time between a session's consecutive events, ignoring any gap over 5 minutes. That last part matters for apps that log from a notification handler — the SDK only rotates session_id on a foreground transition, so without the cap one id can read as a 19-hour session. first_open and install_attribution fire before the id exists and sit outside every session.
| install_attribution | SDK captures the AdServices token at first open; the server resolves it against Apple's attribution API. Props: resolved + Apple's payload (attribution, campaignId, ...) | Acquisition card: Apple Ads vs organic, per-campaign split, ads-attributed revenue |
Events that happen somewhere else
A Watch app has no SDK of its own — it relays to the phone over WatchConnectivity, and the phone stamps its own hardware model, which files the action under iPhone. Mark the relay point and the Devices card files it under Apple Watch instead:
ActivationPal.trackFromWatch("workout_started") // shorthand
ActivationPal.track("set_logged", ["reps": 12], from: .watch)
Origin is .phone, .watch, .widget, .vision or .mac; it lands in props.origin and outranks the hardware model. A device that drives both its phone and its watch is counted under both, so that column can sum past the unique-device total.
Fixed manual — helpers on ActivationPal
Names and prop keys are fixed; the funnel and paywall cards match on them exactly. Never reuse them for other meanings.
| Event | Call | Dashboard use |
|---|---|---|
onboarding_step |
onboardingStep(_ index: Int, id: String, answer: String? = nil) |
One funnel row per step, ordered by index; answer values split under question steps |
onboarding_completed |
onboardingCompleted() |
Onboarded headline; funnel "completed" stage |
paywall_shown |
paywallShown(_ placement: String) |
Placements card (all users) + funnel paywall stage and the Paywall conv. headline (install cohort only) |
paywall_plan_selected |
paywallPlanSelected(_ plan: String) |
Funnel stage, per-plan split |
paywall_purchased |
paywallPurchased(_ plan: String) |
Funnel stage, per-plan split; the Conversion and Paywall conv. headlines; median first_open → first purchase |
paywall_dismissed |
paywallDismissed() |
Event feed and per-user timelines |
Every funnel stage counts unique devices from the range's install cohort (first_open inside the range) — no stage exceeds 100% of installed. paywall_shown is also counted un-cohorted in the placements card.
Purchases, trials, renewals, and churn arrive server-side as rc_* events — revenuecat.md.
Custom
ActivationPal.track("workout_logged", ["type": "run", "duration_min": 42, "pro": true])
- Zero registration: any new name appears in the Events card (count + unique devices), queryable via
get_events. - Names: snake_case, past tense —
workout_logged,export_tapped. Keep the set small and stable; a renamed event is a new event. - Prop values:
String,Bool,Int,Doubleonly — anything else is silently dropped. Prefer low-cardinality strings; props are for slicing, not payloads.
Context on every event
SDK-stamped: app_version, build, os_version, device_model, locale, app_language (the localization the app actually resolved to), timezone, storefront (App Store country; resolves shortly after launch), installed_at, sdk_version, env (debug / testflight / release), stable per-install device_id, per-session session_id. Server-added from the request IP: country, city. These power the device, geo, and language breakdowns.
Delivery
Buffer in memory, persist to disk (survives force-quit), flush every 10 s / at 20 buffered events / on background; failed flushes retry. Never crashes the app, never blocks the main thread.