Skip to article
ActivationPal/docs
Browse docsEvents

Events

The full event vocabulary: automatic, fixed manual, and custom events, plus the context stamped on every one.

View Markdown

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 | The native Swift SDK and Flutter package capture the AdServices token automatically 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)

Flutter uses ActivationPal.trackFromWatch('workout_started') or passes ActivationPalOrigin.watch as the third argument to track.

Native Android uses ActivationPal.trackFromWear("workout_started"); the Devices card labels that origin as Wear OS.

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])
ActivationPal.track('workout_logged', {'type': 'run', 'duration_min': 42, 'pro': true});
ActivationPal.track("workout_logged", mapOf("type" to "run", "duration_min" to 42, "pro" to 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, Double only — anything else is silently dropped. Prefer low-cardinality strings; props are for slicing, not payloads.

Context on every event

Recorded at ingest: platform, app_version, build, os_version, device_model, locale, app_language, timezone, installed_at, sdk_version, env (debug / testflight / release), stable per-install device_id, per-session session_id. Every current SDK stamps platform; the server also recovers it for older Flutter and React Native Android clients from their platform context. Native Swift resolves storefront automatically; Flutter and React Native accept it at configuration when the host app knows it. 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.