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 name | Android | iOS | React Native | Flutter | Funnel step |
|---|---|---|---|---|---|
onboarding_step | onboardingStep(step, index, flow) | onboardingStep(_:index:flow:) | onboardingStep(step, { index, flow }) | onboardingStep(step, index:, flow:) | Onboarding tab, not a step |
onboarding_skipped | onboardingSkipped(step) | onboardingSkipped(_:) | onboardingSkipped(step) | onboardingSkipped(step) | Onboarding tab, not a step |
onboarding_completed | onboardingCompleted() | onboardingCompleted() | onboardingCompleted() | onboardingCompleted() | Onboarding |
paywall_viewed | paywallViewed(placement) | paywallViewed(placement:) | paywallViewed(placement) | paywallViewed(placement:) | Paywall |
paywall_skipped | paywallSkipped(placement) | paywallSkipped(placement:) | paywallSkipped(placement) | paywallSkipped(placement:) | If you add it |
sign_up | signUp(method) | signUp(method:) | signUp(method) | signUp(method:) | Sign-up |
sign_in | signIn(method) | signIn(method:) | signIn(method) | signIn(method:) | Signed in |
sign_up_skipped | signUpSkipped() | signUpSkipped() | signUpSkipped() | signUpSkipped() | Continued as guest |
trial_started | trialStarted(productId) | trialStarted(productId:) | trialStarted(productId) | trialStarted(productId:) | Trial |
purchase | purchase(amount, currency, productId) | purchase(amount:currency:productId:) | purchase(amount, currency, productId) | purchase(amount, currency, productId:) | Paid |
purchase_cancelled | purchaseCancelled(productId) | purchaseCancelled(productId:) | purchaseCancelled(productId) | purchaseCancelled(productId:) | If you add it |
purchase_failed | purchaseFailed(productId, reason) | purchaseFailed(productId:reason:) | purchaseFailed(productId, reason) | purchaseFailed(productId:reason:) | If you add it |
| your own | track(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.
placementsays which paywall:onboarding,launch,feature_gate,settings, or your own name. It's sent as the propertyplacement. Funnel → split by Paywall shows each placement's own funnel.methodsays how the account was made:email,apple,google. Sent asmethod.productIdis your store product ID. Sent asproduct_id.purchasecarries the price as a decimal (AndroidBigDecimal, iOSDecimal, React Native and Flutter a string such as"49.99") and a three-letter currency code. Prices never round.
When to send each one
| Event | Send it |
|---|---|
onboarding_step | When each onboarding screen appears, not when its button is tapped. See Onboarding screens. |
onboarding_skipped | When someone taps Skip, with the screen they tapped it on. |
onboarding_completed | When the last onboarding question or screen is done, before any paywall or sign-up that follows it. |
paywall_viewed | Every time a paywall appears, with its placement. Not for subscribers: if your app shows them no paywall, send nothing. |
paywall_skipped | When 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_up | Right after a new account is created. Not when someone signs in to an account they already had. |
sign_in | When 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_skipped | When 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, purchase | When the store confirms it. Skip both if RevenueCat is connected. |
purchase_cancelled | When someone closes the store's purchase sheet without paying. |
purchase_failed | When 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);
stepis 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/paywallSkippedandsignUp. A paywall closed without buying ispaywallSkipped, neveronboardingSkipped. onboardingCompletedgoes 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
steporflow. 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
onboardingCompletedstill 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
onboardingCompletedsent 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 happened | Send |
|---|---|
| Created a new account (email, Apple, Google, also after being a guest) | signUp(method) |
| Signed in to an account they already had | signIn(method) |
| Chose to go on without an account | signUpSkipped() (SDK 0.3.2+, React Native 0.3.1+; older SDKs: track("sign_up_skipped")) |
| A guest who signs up later, from settings | signUp(method): it counts for the funnel's sign-up step whenever it happens |
| Signed in with a reset password | signIn(method): the account already existed |
| Tried to sign up with an email that's already registered, then signed in | signIn(method) |
| Signed out | setUserId(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.
| Event | When |
|---|---|
first_open | Once per install, on the first launch. This is the install. |
session_start | On launch, or when the app returns after 30 minutes or more in the background. |
app_background | When 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_uporexport.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.
| Android | iOS | React Native | Flutter | What 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() | installId | getInstallId() | 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.