> For the complete AppRefer docs index, see https://apprefer.com/llms.txt (every page as markdown) or https://apprefer.com/llms-full.txt (all docs in one file).

# 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](https://apprefer.com/docs/integrations/revenuecat.md) 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

**Swift**

```swift
await AppRefer.trackEvent("sign_up")

await AppRefer.trackEvent(
    "tutorial_complete",
    properties: ["step_count": 5]
)
```

**Kotlin**

```kotlin
AppRefer.trackEvent("sign_up")

AppRefer.trackEvent(
    "tutorial_complete",
    properties = mapOf("step_count" to 5),
)
```

**Dart**

```dart
await AppReferSDK.trackEvent('sign_up');

await AppReferSDK.trackEvent(
  'tutorial_complete',
  properties: {'step_count': 5},
);
```

**TypeScript**

```typescript
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](#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.

**Swift**

```swift
await AppRefer.setUserId("user_123")
```

**Kotlin**

```kotlin
AppRefer.setUserId("user_123")
```

**Dart**

```dart
await AppReferSDK.setUserId('user_123');
```

**TypeScript**

```typescript
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_revenue` and `original_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_revenue` and `quarantined_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 as `app_opened` or `paywall_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](https://apprefer.com/docs/integrations/openai.md). **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](https://apprefer.com/docs/qualified-trials.md).

## 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+).

**Swift**

```swift
await AppRefer.setAdvancedMatching(
    email: "user@example.com",
    phone: "+14155551234"
)
```

**Kotlin**

```kotlin
AppRefer.setAdvancedMatching(
    email = "user@example.com",
    phone = "+14155551234",
)
```

**Dart**

```dart
await AppReferSDK.setAdvancedMatching(
  email: 'user@example.com',
  phoneNumber: '+14155551234',
);
```

**TypeScript**

```typescript
await AppRefer.setAdvancedMatching({
  email: 'user@example.com',
  phone: '+14155551234',
});
```

You can also pass first name, last name and date of birth. See [Privacy](https://apprefer.com/docs/privacy.md#on-device-hashing) 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 409 `original_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.

---

Source: https://apprefer.com/docs/events · Last updated: 2026-10-19
