Skip to main content

Auto-Tracked Events

The SDK records the events below automatically. You do not need to call track() for any of them, and doing so produces duplicate data — two rows for the same moment, which silently double-counts every metric built on it. These events can be used as:
  • Experiment conversion goals in the Console
  • Webhook triggers for server-side integrations
  • In-app message and survey triggers
  • Analytics filters in dashboards and exports
This page is generated from the SDK source. Every event below is emitted by a real call site in the iOS SDK (82 events). CI (pnpm check:auto-tracked-events) fails if the SDK gains an event that is not listed here, so the page cannot drift out of date.
Every event carries the session and identity context automatically — session_id, the anonymous id, and the user id once identify() has been called — and the device context: platform, OS, app and SDK versions, locale, country and, from iOS SDK 1.0.82 / Android SDK 1.0.54, the device’s IANA time zone (device.timezone, see the event envelope). The Key properties column lists what is specific to that event.

Session & lifecycle

Onboarding

Location answers are reduced at ingest. A location answer inside selection_data or responses is stored and forwarded with only what the geocoding providers’ terms allow. A plain string means the answer was stored as the text the user typed: they typed without picking a suggestion, or, on the SDK releases that resolve a tapped suggestion, they picked one that could not be resolved (the resolve was refused or failed, for example a Mapbox token without permanent access, a Google key without the Geocoding API, a provider error or a network failure, or the suggestion carried no ID). Any other picked answer is an object: {raw_query, timezone, timezone_offset} for a Mapbox permanent answer, {raw_query, place_id} for a Google answer, {raw_query} (the text the user typed) for any other picked answer, keeping only the keys the answer has. The full answer stays on the device (getLocationData). SDK releases that do not resolve a tapped suggestion send a picked answer as {raw_query}; the other two object shapes come only from the releases that do, so which shape you receive depends on the SDK version in the build of your app the user ran.

Permissions

Paywalls & purchases

Purchase event details
  • is_trial: with billingProvider storeKit2 (AppDNA’s own billing), purchase_completed.is_trial is a Boolean on both platforms, and a free-trial start reports price 0. On iOS price is the amount actually charged (the intro price for a paid introductory offer). The RevenueCat and Adapty bridges omit is_trial — an app on revenueCat sees no change in trial event revenue.
  • Late purchases: a purchase_completed reported after the purchase call has ended carries an empty paywall_id (""). On iOS (.storeKit2) these are interrupted, Ask-to-Buy and offer-code purchases, reported whenever StoreKit delivers the transaction — at app start or while the app runs. On Android they are purchases whose verification failed earlier and purchases made outside the app, reported at every return to the foreground (app start included), at identify, during a restore, and on refreshEntitlementCache().
  • is_consumable is sent on both platforms, from different sources. On Android it is true for a product marked Consumable in Monetization → Products. On iOS it comes from the App Store product type: true for a product configured as Consumable in App Store Connect (the console flag is not read).
  • emitted_by is "sdk" on SDK billing events. It is reserved: AppDNA.track drops an emitted_by you pass.
  • cancel_semantics on subscription_canceled: "vanished_not_renewing" (the SDK saw the subscription disappear while not auto-renewing) or "auto_renew_off" (a billing provider webhook reported auto-renew turned off).
  • delivery_id on push events identifies one delivery to one device; it requires an AppDNA server from this release.

Subscriptions

source: "host" is what distinguishes your assertion from a purchase the SDK observed, so the two are never confused in the warehouse. There is no user id in the payload: the SDK attaches the identity it already holds. Whether these count toward your bill is part of your agreement — see Who counts as a paying user.

Web entitlements

Screens, flows & slots

In-app messages

Push

Both platforms report every dismissal. iOS delivers one only for a notification whose category was registered with customDismissAction, so the SDK registers a button-less appdna_default category at configure time and every AppDNA push names it — a push with no action buttons reports its dismissal exactly like one that has them. Android reports dismissals through the notification’s delete intent.A push that names its own category keeps it; only an unset one falls back to appdna_default. If you set a custom category, register it with customDismissAction yourself or that push’s dismissals go unreported on iOS.

Surveys & feedback

There is no “accepted” or “declined” review event, and there cannot be. review_prompt_shown records that the SDK asked the OS for a prompt — which is the only thing either store lets an app know. Apple’s StoreKit reports nothing about what the user did next, and Google’s In-App Review API documents that it withholds the outcome deliberately, so that an app cannot condition behaviour on whether a review was left.Both stores forbid the only mechanism that could tell you: a pre-screen dialog (“Enjoying this app?” → Yes / Not really) used to gate who sees the real prompt. Never build one.So there is no review-conversion funnel to measure. Compare review_prompt_shown against your store listing’s review count instead.

Onboarding step hooks

A step can call a hook — your onBeforeStepRender / onBeforeStepAdvance delegate, or a server-side one — before it renders or before it advances. These four events say what happened, so a slow or failing hook is visible as a funnel drop rather than a mystery.

Onboarding chat step

Experiments

Identity


SDK health

coding_path is the field that was wrong and why — for example steps[9].content_blocks[3]: Dictionary<String, Any> was null. A count alone would say that something is wrong and nothing about what, so the path is what the event carries. Reported once per distinct problem per process: one malformed document is decoded on every bootstrap and every presentation, and an unbounded event would bill for thousands of identical rows. The same information reaches your own code through AppDNALifecycleDelegate.onConfigurationIssue.

Platform differences

The tables above are generated from the iOS SDK. Android, Flutter and React Native emit the same event names for the same moments — that parity is enforced by the shared behavioural fixtures in packages/sdk-shared-fixtures/ — but a platform may not implement every surface yet. If an event you expect never arrives, check the platform SDK page under SDKs before implementing it yourself.

Defining your own events

Anything not listed above is yours to define — the product moments no SDK can infer: the action that means a user got value, what counts as activated, the loop a healthy user repeats. See Events for the track() contract, and Analytics & feedback for how they reach your dashboards.
Before adding a custom event, search this page for the moment you are trying to capture. The two most common duplicates are onboarding_completed and subscription_started — both are already here.