> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getflowpilot.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversions and revenue

> Attribute purchases to the flow that drove them with trackConversion.

When a user buys after seeing a paywall flow, you want that revenue to roll up against the flow (and experiment variant) that drove it. `trackConversion` emits a `conversion` event attributed to the flow's full context, so the dashboard can report revenue per flow, placement, and variant.

## Track a conversion

Call `FlowPilot.trackConversion(...)` when a purchase completes, typically from your IAP listener (RevenueCat, `expo-in-app-purchases`, StoreKit bridge, and so on). The event is attributed to the **most-recently-presented** flow, so it works even when the purchase callback fires seconds after the paywall was dismissed.

```ts theme={null}
// Minimal: amount + currency
FlowPilot.trackConversion(9.99, 'USD');

// With a product id and custom metadata
FlowPilot.trackConversion(9.99, 'USD', 'premium_yearly', {
  trial: true,
  source: 'revenuecat',
});
```

### Signature

```ts theme={null}
FlowPilot.trackConversion(
  amount: number,
  currency: string,
  productId?: string,
  metadata?: Record<string, unknown>,
): void
```

| Parameter   | Type                       | Notes                                                                                                        |
| ----------- | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `amount`    | `number`                   | The revenue amount. Emitted on the dedicated `revenue` field.                                                |
| `currency`  | `string`                   | ISO currency code. Normalised to uppercase before send (`usd` becomes `USD`) so rollups do not fork on case. |
| `productId` | `string?`                  | Merged into the event under `product_id`.                                                                    |
| `metadata`  | `Record<string, unknown>?` | Merged into the event properties as-is. On a `product_id` key collision, your metadata value wins.           |

## How attribution works

`FlowPilot.trackConversion` routes the event through the most-recently-started session's analytics batcher, which stamps it with that flow's `flow_id`, `flow_version_id`, `placement_id`, `experiment_id`, and `variant_id`. That is why you can call it from a purchase callback that runs after the flow was dismissed (the presenter set its session back to `null`).

If you are holding a specific `FlowSession` and want to attribute to it explicitly (rather than the last one started), call the session method directly with the same arguments:

```ts theme={null}
session.trackConversion(9.99, 'USD', 'premium_yearly');
```

<Warning>
  If no flow has been presented yet, `trackConversion` logs a warning and **drops** the event, because the backend requires non-empty flow context. Always present a flow before calling it. Conversions from purchases that did not originate in a FlowPilot flow should go to your own analytics, not here.
</Warning>

## Example: a RevenueCat listener

```tsx theme={null}
import Purchases from 'react-native-purchases';
import { FlowPilot } from '@flowpilotjs/react-native-sdk';

Purchases.addCustomerInfoUpdateListener((info) => {
  const entitlement = info.entitlements.active['premium'];
  if (entitlement) {
    FlowPilot.trackConversion(
      Number(entitlement.priceString?.replace(/[^0-9.]/g, '') ?? 0),
      'USD',
      entitlement.productIdentifier,
      { source: 'revenuecat' },
    );
  }
});
```

<Note>
  The example reads price loosely for illustration. In production, source the real amount and currency from your purchase receipt rather than parsing a display string.
</Note>

## Common mistakes

* **Calling before any flow is presented.** The event is dropped with a warning. Present a paywall or onboarding flow first.
* **Tracking unrelated purchases here.** `trackConversion` attributes to the last presented flow. A purchase from elsewhere in the app would be misattributed. Only call it for flow-driven conversions.
* **Inconsistent currency casing.** Not actually a problem: the SDK uppercases currency for you. But keep your own analytics consistent too.
* **Expecting it to return a result.** It is fire-and-forget (`void`) and batched with other analytics. There is no per-call confirmation.

## Troubleshooting

* **Conversion not appearing in the dashboard.** Confirm a flow was presented before the call (otherwise it is dropped), and that events are flushing. Set `logLevel: 'debug'` to see the batch flush. See [Analytics](/expo-sdk/analytics).
* **Revenue attributed to the wrong flow.** `trackConversion` attributes to the most-recently-started session. If two flows were presented close together, hold the specific `FlowSession` and call `session.trackConversion(...)` to be explicit.

## Related pages

* [Analytics integration](/expo-sdk/analytics)
* [Variables and context](/expo-sdk/variables-and-context)
* [Event taxonomy](/reference/event-taxonomy)
* [Reading experiment results](/dashboard/reading-results)
