Skip to content
InfluToDocs

Install the SDK

Add InfluTo to a Flutter app

Add the influto package to a Flutter app, detect referred installs, accept typed codes, and connect RevenueCat or report store purchases with influto_iap.

The influto package adds creator attribution to a Flutter app on iOS and Android. It tells you whether an install came from a creator's link or code, lets people type a creator's code, and gets each purchase to InfluTo — through RevenueCat, or straight from the App Store and Google Play with the companion package influto_iap. Plan for an afternoon; most of it goes into the purchase side.

Package influto, plus the optional companion influto_iap for automatic purchase capture
Latest version 1.1.1
Registry pub.dev
Requires Flutter 3.22+, Dart 3.4+
Dependencies http and shared_preferences (influto_iap adds in_app_purchase)
Source github.com/influto/influto-flutter (MIT)

Install

Terminal
flutter pub add influto
# optional: automatic store-direct purchase capture
flutter pub add influto_iap

Add influto_iap only if your app is store-direct (no RevenueCat) and sells through the in_app_purchase plugin. RevenueCat apps using purchases_flutter only need influto.

Initialize

Dart
import 'package:influto/influto.dart';

// Call once at startup
await InfluTo.instance.initialize(
  InfluToConfig(
    apiKey: 'YOUR_API_KEY',
    // RevenueCat apps: let the SDK tag the subscriber
    // revenueCatHook: (attrs) => Purchases.setAttributes(attrs),
  ),
);

Call initialize once at startup and wait for it before calling anything else — if it hasn't succeeded, checkAttribution and reportPurchase throw an InfluToException. It throws when it fails (a wrong key, no network), so catch the error, log it, and carry on; the next launch tries again. Don't hold up your first frame for it. A typical startup, in order:

main.dart
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:influto/influto.dart';
import 'package:purchases_flutter/purchases_flutter.dart'; // only if you use RevenueCat

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Purchases.configure(PurchasesConfiguration('appl_…')); // RevenueCat apps: configure it first
  runApp(const MyApp());
  unawaited(startInfluTo());
}

Future<void> startInfluTo() async {
  try {
    await InfluTo.instance.initialize(InfluToConfig(
      apiKey: const String.fromEnvironment('INFLUTO_API_KEY'),
      appVersion: '1.4.0',
      revenueCatHook: (attrs) => Purchases.setAttributes(attrs),
    ));
  } catch (e) {
    debugPrint('InfluTo initialize failed: $e'); // retried on the next launch
    return;
  }
  final attribution = await InfluTo.instance.checkAttribution();
  await InfluTo.instance.identifyUser(await Purchases.appUserID);
}

The API key starts with it_ and is on Setup & SDK; one key works for development and production. Keep it out of committed source — for example pass it at build time with --dart-define=INFLUTO_API_KEY=it_… and read it with String.fromEnvironment.

Parameter Type Default What it does
apiKey String required The app's SDK key (it_…)
debug bool false Prints [InfluTo] lines with debugPrint
appVersion String? null (sent as 'unknown') Your app version, sent with the startup call
autoCapture bool true Store-direct apps: lets the influto_iap observer report purchases. Set false if you call reportPurchase yourself
revenueCatHook Future<void> Function(Map<String, String>)? null RevenueCat apps: how the SDK sets the subscriber attributes
httpClient http.Client? null A custom HTTP client, for tests

Detect referred users

Dart
final attribution = await InfluTo.instance.checkAttribution();
if (attribution.attributed) {
  // User came from a referral — show a special offer.
  print('Referred by ${attribution.referralCode}');
}

AttributionResult has these fields:

Field Meaning
attributed true when the install came from a creator's link or code
referralCode The creator's code, uppercase
attributionMethod 'ip_device_match' (link-click matching) or 'manual_entry' (a code the user applied)
clickedAt When the link was clicked, or the code applied
message A short explanation, for logs

Attribution is decided once per install and stored on the device; later calls return the stored answer without a request. Only a failed request is retried, on the next call. A reinstall starts over.

checkAttribution is fail-soft: on a network or server error it returns attributed: false and stores nothing. Link-click matching only counts taps from the last 72 hours — see attribution. To show referred users a better offer, see Reward referred users.

Identify the user

Call identifyUser as soon as you know who the user is, with the same ID your purchases are keyed on — for RevenueCat, the app user ID:

Dart
await InfluTo.instance.identifyUser(await Purchases.appUserID);

It matters for three things: InfluTo uses it to credit a RevenueCat purchase when the influto_code attribute didn't make it, checkAccess uses it for free-access codes, and reportPurchase sends it by default. Call it once per sign-in, ideally in the user's first session — not from build.

Track events

Dart
await InfluTo.instance.trackEvent(TrackEventOptions(
  eventType: 'paywall_viewed',
  appUserId: userId,
  properties: {'placement': 'onboarding'},
));

Events show on Real-Time Events and help you read the funnel. They never create conversions or commission — only validated purchases do. Every call gets an event ID, so an accidental double call counts once. For trial_started, subscription_purchased and subscription_renewed the ID is derived from the event, user and properties, so the same event counts once even across launches; include something that changes per occurrence (a transaction ID, say) in properties when the event can repeat. For your own once-only events, pass eventId.

Let users type a code

Dart
// Validate + apply a code the user typed
final result = await InfluTo.instance.applyCode('FITGURU30');
if (result.valid && result.applied == true) {
  // Unlock the offer
}

// Or the ready-made widget: ReferralCodeInput(onApplied: (result) { … })
// Free-access codes (after identifyUser): (await setReferralCode(code)).grantsAccess
// == true → unlock premium, and gate premium on (await checkAccess()).hasAccess.

applyCode(code, appUserId: …) validates the code and applies it if it's valid; the result's applied tells you whether it was applied. Codes are trimmed and uppercased for you. An invalid code comes back with valid: false and an errorCode: INVALID_FORMAT, CODE_NOT_FOUND, CODE_EXPIRED or NETWORK_ERROR. If you build your own field, write your own copy: message and influencer can contain the creator's personal name.

The ready-made field is the ReferralCodeInput widget (Material). It validates as the user types and applies the code when they tap the button:

Dart
ReferralCodeInput(
  appUserId: userId,
  onApplied: (result) { /* the code is applied — unlock the offer */ },
)
Parameter Default Notes
appUserId identified user The user to bind the code to; free-access codes need one
autoPrefill / autoValidate true / false Pre-fill the code from a link-attributed install, and validate it right away
showCampaignName / showReferrerName false / false Show a creator's name only with their consent
title, placeholder, applyLabel, validMessage, invalidMessage — Texts, for translation
onValidated, onApplied — Called after each validation, and after the code is applied

More on the UX: Let users enter a referral code. All parameters: UI components.

Purchases

InfluTo only pays commission on purchases it has verified with the store. Choose exactly one path per platform — see Choose a validation path.

RevenueCat

Pass a revenueCatHook, as in the startup example above. When checkAttribution finds a referral, or a code is applied, the SDK calls it with influto_code (the code) and influto_referral (the string "true"). Then connect the webhook — see RevenueCat.

Store-direct with influto_iap

Create one InfluToPurchaseObserver and enable it after initialize has finished:

Dart
import 'package:influto_iap/influto_iap.dart';

final observer = InfluToPurchaseObserver(
  oneTimeProductIds: {'coins_100', 'remove_ads'}, // Android one-time products; omit if you only sell subscriptions
);
final sync = await observer.enable(); // listens for new purchases + back-fills existing ones

The observer only runs when the app is set to store-direct in Settings → Validation and autoCapture is true; otherwise enable() does nothing, so the same build is safe on RevenueCat apps. Each purchase is reported once. enable() returns a PurchaseSyncResult with fetched, sent and failed counts; syncExisting(), disable() and dispose() are there for manual control.

What the observer does in your app:

  • It completes pending purchases (completePurchase), so the store doesn't redeliver or refund them. Deliver content from your own purchaseStream listener as usual.
  • The back-fill calls restorePurchases(), so your own listener also sees restored purchases at startup. Call enable(backSync: false) if you don't want that.
  • On iOS it switches in_app_purchase to StoreKit 2, which store-direct needs.

To report purchases yourself instead, set autoCapture: false, skip the observer, and call reportPurchase:

Dart
// Store-direct only (no RevenueCat). For automatic capture add influto_iap and
// call InfluToPurchaseObserver().enable() after initialize(). Or report manually:
await InfluTo.instance.reportPurchase(
  platform: 'ios',                 // or 'android'
  signedTransaction: jws,          // iOS: StoreKit 2 JWS
  // purchaseToken: token,         // Android: Play Billing purchaseToken
);
  • platform is required: 'ios' or 'android'. On iOS pass the StoreKit 2 signed transaction, details.verificationData.serverVerificationData — with in_app_purchase on StoreKit 2 (InAppPurchaseStoreKitPlatform.enableStoreKit2() makes sure of it); on Android pass (details as GooglePlayPurchaseDetails).billingClientPurchase.purchaseToken.
  • It throws an InfluToException. retryable is true for 503 (the exchange rate was briefly unavailable) — retry later; notConfigured is true for 400 (the platform isn't set to store-direct yet).
  • Android one-time and consumable products need productId, plus price and currency from the product details; without a price the purchase is recorded at $0. A 409 means the purchase isn't in the PURCHASED state yet.

Set-up, credentials and testing: Store-direct.

Free-access codes

Free-access codes give a user premium without a purchase. Redeem one with setReferralCode(code, appUserId: userId) after identifyUser, or pass the user ID — a code with no user is refused. The SetCodeResult tells you what happened: grantsAccess, freeAccess, entitlement and expiresAt. applyCode doesn't return these flags; after it, result.campaign?.campaignType == 'FREE_ACCESS' tells you the code was a free-access code.

Gate premium on your store entitlement or InfluTo's answer:

Dart
final premium = entitlementActive || (await InfluTo.instance.checkAccess()).hasAccess;

checkAccess(appUserId: …) uses the identified user when you don't pass one, caches a positive answer for 5 minutes (across launches too), never caches a negative one, and returns hasAccess: false on errors. See Free access codes.

Verify

After the first successful initialize, Setup & SDK shows "Integrated — SDK connected" with the platform and SDK version it last saw. With debug: true the SDK prints each step, and Real-Time Events lists your events and purchases. For the full end-to-end test and the integration status checklist, see Verify the integration.

To test attribution again on the same device, clearAttribution() forgets the stored attribution, code and user ID. It's for testing only — a reinstall gives a cleaner start.

Flutter notes

  • One instance. Everything goes through InfluTo.instance; there are no static calls.
  • Order matters: configure RevenueCat, then initialize, then checkAttribution; enable the influto_iap observer only after initialize has finished, or it stays off.
  • const configs: InfluToConfig has a const constructor, but a closure such as (attrs) => … isn't a constant — drop const when you pass a revenueCatHook.
  • No automatic RevenueCat tagging. Unlike React Native, Flutter needs the revenueCatHook.
  • Android one-time products go in the observer's oneTimeProductIds, not in InfluToConfig.