Get started

Events reference

Every call the SDK offers, on each platform, with the event name that reaches Unolink. Funnels are per app: make as many as you like under Funnel → New funnel, and pick any of these names as steps, your own included. Each step counts installs that sent the event at any time, so apps whose screens come in a different order still get a meaningful funnel. Switch on Count steps only in this order in a funnel to count a step only when it happens after the one before it.

Standard events

Event nameAndroidiOSReact NativeFlutterFunnel step
onboarding_steponboardingStep(step, index, flow)onboardingStep(_:index:flow:)onboardingStep(step, { index, flow })onboardingStep(step, index:, flow:)Onboarding tab, not a step
onboarding_skippedonboardingSkipped(step)onboardingSkipped(_:)onboardingSkipped(step)onboardingSkipped(step)Onboarding tab, not a step
onboarding_completedonboardingCompleted()onboardingCompleted()onboardingCompleted()onboardingCompleted()Onboarding
paywall_viewedpaywallViewed(placement)paywallViewed(placement:)paywallViewed(placement)paywallViewed(placement:)Paywall
paywall_skippedpaywallSkipped(placement)paywallSkipped(placement:)paywallSkipped(placement)paywallSkipped(placement:)If you add it
sign_upsignUp(method)signUp(method:)signUp(method)signUp(method:)Sign-up
sign_insignIn(method)signIn(method:)signIn(method)signIn(method:)Signed in
sign_up_skippedsignUpSkipped()signUpSkipped()signUpSkipped()signUpSkipped()Continued as guest
trial_startedtrialStarted(productId)trialStarted(productId:)trialStarted(productId)trialStarted(productId:)Trial
purchasepurchase(amount, currency, productId)purchase(amount:currency:productId:)purchase(amount, currency, productId)purchase(amount, currency, productId:)Paid
purchase_cancelledpurchaseCancelled(productId)purchaseCancelled(productId:)purchaseCancelled(productId)purchaseCancelled(productId:)If you add it
purchase_failedpurchaseFailed(productId, reason)purchaseFailed(productId:reason:)purchaseFailed(productId, reason)purchaseFailed(productId:reason:)If you add it
your owntrack(name, properties)track(_:properties:)track(name, properties)track(name, properties)If you add it

Every argument except a purchase's amount and currency is optional, and every call also takes extra properties.

  • placement says which paywall: onboarding, launch, feature_gate, settings, or your own name. It's sent as the property placement. Funnel → split by Paywall shows each placement's own funnel.
  • method says how the account was made: email, apple, google. Sent as method.
  • productId is your store product ID. Sent as product_id.
  • purchase carries the price as a decimal (Android BigDecimal, iOS Decimal, React Native and Flutter a string such as "49.99") and a three-letter currency code. Prices never round.

When to send each one

EventSend it
onboarding_stepWhen each onboarding screen appears, not when its button is tapped. See Onboarding screens.
onboarding_skippedWhen someone taps Skip, with the screen they tapped it on.
onboarding_completedWhen the last onboarding question or screen is done, before any paywall or sign-up that follows it.
paywall_viewedEvery time a paywall appears, with its placement. Not for subscribers: if your app shows them no paywall, send nothing.
paywall_skippedWhen someone closes a paywall without buying: its close button, a swipe down or the back gesture. Not while a purchase is still waiting for approval (Ask to Buy, a bank's 3-D Secure check).
sign_upRight after a new account is created. Not when someone signs in to an account they already had.
sign_inWhen someone signs in to an account they already had: a returning user (a reinstall, a new phone, after signing out). Journeys tags them returning and filters by it. SDK 0.3.0 or later.
sign_up_skippedWhen someone chooses to go on without an account on a sign-up screen (guest mode). Journeys tags them guest, and a funnel with a sign-up step shows them as taking another path, not as a drop-off.
trial_started, purchaseWhen the store confirms it. Skip both if RevenueCat is connected.
purchase_cancelledWhen someone closes the store's purchase sheet without paying.
purchase_failedWhen the payment fails. reason is your own short description.

Onboarding screens

The Onboarding tab shows which screen people leave on and how long each one takes. Send one call per screen as it appears, one on Skip, and onboardingCompleted at the end:

// iOS (SwiftUI)
GoalScreen().onAppear { Unolink.onboardingStep("goal", index: 0) }
Button("Skip") { Unolink.onboardingSkipped("goal") }
// Android (Compose)
LaunchedEffect(Unit) { Unolink.onboardingStep("goal", index = 0) }
// React Native
useOnboardingStep("goal", { index: 0 })   // or Unolink.onboardingStep("goal", { index: 0 }) in a useEffect
// Flutter, in initState
Unolink.onboardingStep('goal', index: 0);
  • step is a fixed ID per screen: goal, notifications, plan. Same rules as event names. Give branches their own IDs. A pager or a single-screen onboarding sends one per page.
  • Only the onboarding's own screens. The paywall and the sign-up screen aren't onboarding screens, even when they are steps of the same flow: they send paywallViewed / paywallSkipped and signUp. A paywall closed without buying is paywallSkipped, never onboardingSkipped.
  • onboardingCompleted goes right after the last onboarding screen, before the paywall or sign-up that follows, even when the account is created at the end of the flow. Someone who sees the paywall has finished onboarding.
  • index (optional) is the screen's position, 0 to 999. With it, the tab can show the furthest screen reached as well as the last one seen.
  • flow (optional) names the onboarding, when you run more than one: an A/B variant, a new version, a re-onboarding. Each flow gets its own list.
  • These are the only properties these events take. Anything else gets the event rejected.
  • Never put user data in step or flow. They're screen names, shown on the dashboard.
  • They're free. An onboarding costs one event, its onboardingCompleted, however many screens it has. Each install can send up to 100 of them, for up to 40 different screens.
  • Back navigation, app kills and relaunches need nothing special. Unolink works out each install's status: completed, skipped on a screen, left on its last screen (nothing new for 24 hours), or still going. An app that sends only onboardingCompleted still shows how many completed; screens are needed to see where people leave. Installs that sent no onboarding event at all are shown as not tracked, never as drop-off.

These calls need SDK 0.2.0 or later. On older versions, track("onboarding_step", { step: "goal" }) sends the same event.

How events are counted

  • Funnels count people, not events. Each step counts the installs that sent the event at least once, so a paywall seen five times, or onboardingCompleted sent again after going back, counts once per person.
  • Any order, by default. A step counts whenever it happened. Turn on Count steps only in this order in a funnel when the order matters; then a step counts only at or after the one before it.
  • Every event still counts toward your plan. Repeats are recorded (Journeys shows each one) and each counts toward the monthly allowance, apart from the free ones below.
  • Times are the phone's, unless they're more than 7 days old or more than an hour in the future; then the time it arrived is used.

Guests and accounts

Report RevenueCat's own app user ID every time it changes: for a guest, RevenueCat's anonymous ID, then the ID of the account they create or sign in to. connectRevenueCat (SDK 0.3.2 or later) does it for you; otherwise call setUserId with it. Every ID an install has reported counts, so a purchase made as a guest, its renewals, and purchases RevenueCat later moves to another account all stay with the install that bought.

What happenedSend
Created a new account (email, Apple, Google, also after being a guest)signUp(method)
Signed in to an account they already hadsignIn(method)
Chose to go on without an accountsignUpSkipped() (SDK 0.3.2+, React Native 0.3.1+; older SDKs: track("sign_up_skipped"))
A guest who signs up later, from settingssignUp(method): it counts for the funnel's sign-up step whenever it happens
Signed in with a reset passwordsignIn(method): the account already existed
Tried to sign up with an email that's already registered, then signed insignIn(method)
Signed outsetUserId(null)

New or returning with Apple or Google? The sign-in result usually says. Firebase: additionalUserInfo.isNewUser. Supabase: a user whose created_at is within a few seconds of last_sign_in_at was just created.

Automatic events

The SDK sends these itself. They're free: they never count toward your plan.

EventWhen
first_openOnce per install, on the first launch. This is the install.
session_startOn launch, or when the app returns after 30 minutes or more in the background.
app_backgroundWhen the app goes to the background.

Custom events

track(name, properties) sends anything else, and custom events can be funnel steps too.

  • Names: start with a letter, then letters, digits, _ and ., at most 64 characters. level_up or export.pdf. An invalid name is dropped and logged.
  • Properties: at most 20 per event. Values are strings (up to 200 characters), numbers or booleans, 4 KB in total.
  • Never put names, emails, phone numbers or other personal data in properties.

User and app state

These send no event of their own.

AndroidiOSReact NativeFlutterWhat it does
setUserId(id)setUserId(_:)setUserId(id)setUserId(id)Your app's user ID; with RevenueCat, RevenueCat's own app user ID. Stored only as a one-way hash per app. Call it again whenever it changes (guest → account): every ID counts. Pass null (nil) on logout. At most 256 characters.
connectRevenueCat { }connectRevenueCat { }connectRevenueCat(Purchases)connectRevenueCat(…)Keeps the user ID equal to RevenueCat's app user ID, guests included (SDK 0.3.2+). See RevenueCat. Use it or setUserId, not both.
revenueCatUserChanged()revenueCatUserChanged()Sends RevenueCat's new ID at once: call it after logIn and logOut (SDK 0.3.2+). React Native and Flutter notice by themselves.
setDeferredDeepLinkListener { }onDeferredDeepLink(_:)addDeferredDeepLinkListener(fn)setDeferredDeepLinkListener(fn)Called once per install that came from a Unolink link. Android only in practice.
getPendingDeferredDeepLink(), consumeDeferredDeepLink()getPendingDeferredDeepLink(), consumeDeferredDeepLink()getPendingDeferredDeepLink(), consumeDeferredDeepLink()The link to open once onboarding or sign-in is done, then mark as opened. It waits until consumed, even if the app is closed in between, for up to 30 days (SDK 0.3.3+; React Native 0.3.1+ kept it for one launch).
isConfigured()Whether configure has run with a key (React Native 0.3.1+).
getInstallId()installIdgetInstallId()getInstallId()The random ID of this install.
flush()flush()flush()flush()Sends queued events now.
setEnabled(bool)setEnabled(_:)setEnabled(bool)setEnabled(bool)Turns tracking off (queued events are deleted) or back on.

RevenueCat

When RevenueCat is connected, it's the source of truth for money. It reports trials, purchases, renewals, cancellations, refunds, expirations and billing issues, even when the app isn't open. App calls to purchase and trialStarted are ignored, so nothing counts twice. Your app only needs connectRevenueCat (or setUserId with RevenueCat's ID).

Plan quota

The SDK is part of the Pro and Studio plans. Every event your app sends counts toward the monthly event allowance: 1 million on Pro, 5 million on Studio. That includes the standard events above and your custom ones. The automatic events (first_open, session_start, app_background) and onboarding screens (onboarding_step, onboarding_skipped) are free, and so are purchases, renewals and refunds that arrive from RevenueCat. Going over the allowance blocks nothing; above twice the allowance, custom events are refused until the next month, while standard, onboarding and automatic events are always kept.