Skip to main content
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.
// 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

FlowPilot.trackConversion(
  amount: number,
  currency: string,
  productId?: string,
  metadata?: Record<string, unknown>,
): void
ParameterTypeNotes
amountnumberThe revenue amount. Emitted on the dedicated revenue field.
currencystringISO currency code. Normalised to uppercase before send (usd becomes USD) so rollups do not fork on case.
productIdstring?Merged into the event under product_id.
metadataRecord<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 presentPlacement already returned. 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:
session.trackConversion(9.99, 'USD', 'premium_yearly');
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.

Example: a RevenueCat listener

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' },
    );
  }
});
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.

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.
  • 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.