Events
Track what people do in your app for product analytics, and choose which of those events are sent to ad networks as conversions.
Where events come from
| Source | How | Typical events |
|---|---|---|
| Your app | trackEvent() in the SDK | sign_up, tutorial_complete, paywall_viewed, level_complete |
| Store purchases | AppRefer Billing store notifications, or RevenueCat webhooks | Trials, first payments, renewals, refunds, cancellations (no app code needed) |
| Your website backend | POST /api/v1/apps/{appId}/web-events | Verified website purchases and funnel signals (see below) |
Purchases are best reported from the store side. If your app also sends revenue events, turn on RevenueCat Only in app settings to ignore revenue the app reports itself, so nothing is counted twice.
Tracking an event
await AppRefer.trackEvent("sign_up")
await AppRefer.trackEvent(
"tutorial_complete",
properties: ["step_count": 5]
)Every event can also carry revenue and currency (see Revenue on events). Names starting with _ are reserved for the SDK.
Delivery and the event queue
From SDK 0.9.0 every event is saved on the device before it is sent, with a stable eventId, and delivered at least once:
- Network errors, timeouts (408), rate limits (429) and server errors (5xx) are retried with back-off, and again when the app launches or comes to the foreground.
- The queue holds up to 1,000 events for up to 7 days. Events the server rejects for another reason are removed.
- The server de-duplicates on
eventId, so a retry never counts twice.
trackEventResult() takes the same arguments and tells you what happened: sent, queued (saved, will retry) or dropped (the SDK isn't configured, collection is off, the kill switch is on, or the app called reset() and hasn't configured for the next user yet). clearPendingEvents() empties the queue.
Linking events to your user
Call setUserId() after login with your user ID (the same one your purchases use). From SDK 0.9.1 each event records the app_user_id that was set when it was tracked and keeps it through retries, restarts and account switches, so an event queued before a sign-out still belongs to the right person.
await AppRefer.setUserId("user_123")Revenue on events
Pass revenue as a number (a numeric string such as "9.99" is also accepted by the API) and currency as a three-letter ISO code. AppRefer stores revenue in USD:
- Amounts in other currencies are converted with a daily exchange rate. The original amount and currency are kept as
original_revenueandoriginal_currency. - Amounts that can't be converted, aren't numbers, are negative or are over $2,000 are quarantined: the event is stored with
quarantined_revenueandquarantined_reason, but it isn't counted as revenue and isn't sent to ad networks. This catches mistakes such as a local price sent as USD. - With RevenueCat Only on, events with revenue (and
purchase,subscribe,startTrial,intro_purchase,promo_purchase) from the app are ignored.
Which events reach ad networks
Every event is stored for your analytics. Only some are sent to ad networks:
- Meta and TikTok receive an event only if it has a mapping you set in Settings → Event mapping, a default mapping (table below), or is already one of the network's standard event names (for example
CompleteRegistration). Everything else, such asapp_openedorpaywall_viewed, stays in AppRefer. Clearing a mapping stops that event being sent. - Google Ads receives an event only when you have mapped it to a conversion action in Settings → Conversion actions. There is no fallback action, so an unmapped event never pollutes another action's bidding.
- ChatGPT Ads receives trial, subscription and order events by default; see ChatGPT Ads. X Ads receives only verified website purchases, and only when turned on for the app.
- An event goes to the network that drove the install. Events from organic installs go to every connected network (Google only when there is a
gclid), so the networks see the full conversion picture. - Nothing is sent for test keys (
pk_test_), for installs without tracking permission, for quarantined revenue, or for devices AppRefer has no install record for.
Default mappings
Event names are matched exactly, then in lower case. You can override any row per app.
| AppRefer event | Meta | TikTok |
|---|---|---|
| install, qualified_install | Lead | Lead |
| sign_up | Lead | Lead |
| complete_registration | CompleteRegistration | CompleteRegistration |
| startTrial, qualified_trial | StartTrial | StartTrial |
| purchase, intro_purchase, promo_purchase (one-time) | Purchase | Purchase |
| Subscription first payment or trial conversion | Subscribe | Purchase |
| Subscription renewal | Subscribe | Purchase |
| subscribe | Subscribe | Subscribe |
| add_to_cart | AddToCart | AddToCart |
| initiate_checkout | InitiateCheckout | InitiateCheckout |
| search | Search | Search |
| view_content | ViewContent | ViewContent |
For installs attributed to Meta, store purchases are split by lifecycle: a subscription's first payment, its trial conversion and each renewal are sent as Subscribe, and one-time purchases as Purchase. Override the two subscription rows if your ad sets optimise for Purchase. Trial starts can be held back first; see Qualified trials.
Deduplication
Each forwarded event carries a stable event ID per network, so a retry or a purchase reported by both the store and the app is counted once. Nothing to configure.
Advanced matching
After sign-up or login, send the user's email or phone so ad networks can match conversions more often. Values are SHA-256 hashed on the device, and the call does nothing for installs without tracking permission (SDK 0.6.0+).
await AppRefer.setAdvancedMatching(
email: "user@example.com",
phone: "+14155551234"
)You can also pass first name, last name and date of birth. See Privacy for how each value is normalised before hashing.
Verified website payments
If people pay on your website after tapping an ad, your payment backend reports those payments with POST /api/v1/apps/{appId}/web-events, using a private organisation API key with apps:write. It is off by default; turn it on with PATCH /api/v1/apps/{appId} and {"webEvents":{"enabled":true}}.
Send eventId, arClickId, an anonymous deviceId, the original timestampMs, eventType (purchase, renewal or refund), revenue in major units, an ISO currency, a clean HTTPS sourceUrl and optionally emailSha256 and fbp. A renewal names its first purchase in originalTransactionId; a refund names the payment it reverses, carries negative revenue, and refunds can never add up to more than that payment.
The click (arClickId, passed to your site by the tracking link) must be a verified Meta, Google, TikTok, ChatGPT Ads or X Ads click for the same app, from a website-funnel link whose destination matches sourceUrl. Only first purchases are sent to ad networks; renewals and refunds update revenue reporting only.
Real, settled payments only
Send only settled live payments, with any consent your site needs. No sandbox, test, zero-value or unpaid receipts. A 202 means AppRefer has stored the receipt, not that a network has credited it. Retry with the identical event ID and payload; a changed payload for the same ID is refused, and a renewal or refund whose original purchase isn't known yet returns 409original_transaction_unavailable and can be retried later.Website funnel signals
The same endpoint accepts sign_up (no money, sent to Meta as CompleteRegistration), checkout_started (selected price, Meta InitiateCheckout) and start_trial (the plan price as predicted value, Meta StartTrial and ChatGPT Ads trial_started). Send start_trial only once your payment backend confirms a card-required trial; a later paid conversion is a separate purchase. These signals need the same eligible click, use stable event IDs and add no revenue.
Website-funnel redirects pass on offer=trial or offer=control when present and always add ar_click_id and ar_link_id, so your site can use them for experiment assignment.