Install the SDK
Add InfluTo to a React Native or Expo app
Install the InfluTo SDK in a React Native or Expo app, detect referred installs, accept typed codes, and send purchases through RevenueCat or the stores.
@influto/react-native-sdk adds creator attribution to a React Native or Expo app. 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. Plan for an afternoon; most of it goes into the purchase side.
| Package | @influto/react-native-sdk (UI components: @influto/react-native-sdk/ui) |
| Latest version | 1.7.0 |
| Registry | npm |
| Requires | React Native 0.60+ and React 16.8+, @react-native-async-storage/async-storage 2.x |
| Works with | Expo (managed and bare) and bare React Native, iOS and Android |
| Source | github.com/influto/influto-react-native (MIT) |
Install
npm install @influto/react-native-sdk
# AsyncStorage is a native module — install it in the app too
npm install @react-native-async-storage/async-storageAsyncStorage holds the SDK's local state. Install it in your app as well, so autolinking picks up its native module. In a bare React Native project, run pod install in ios/ afterwards.
On Expo there is no config plugin to add: the SDK is plain JavaScript on top of AsyncStorage, so it runs in Expo Go, development builds and production builds alike.
The SDK also uses these packages when your app already has them. You don't import anything for this:
| Package | What the SDK does with it |
|---|---|
react-native-purchases |
Sets the RevenueCat attributes influto_code and influto_referral when a referral is found or a code is applied |
expo-iap or react-native-iap |
Captures store purchases on store-direct apps (expo-iap is used if both are installed) |
react-native-device-info |
Adds the device brand and model to install tracking |
Store libraries contain native code, so test purchases in a development build rather than Expo Go.
Initialize
import InfluTo from '@influto/react-native-sdk';
// Call once at startup (e.g. in App.tsx)
await InfluTo.initialize({
apiKey: 'YOUR_API_KEY',
debug: __DEV__,
});
// RevenueCat apps: nothing else to wire — if react-native-purchases is
// installed, the SDK sets the influto_code subscriber attribute itself.Call initialize once, as early as possible, and wait for it before calling anything else. If it hasn't succeeded, checkAttribution and reportPurchase throw "not initialized" and the other methods return empty results.
initialize throws when it fails — a wrong key, no network. Catch it, log it, and carry on: the next launch tries again. A typical startup, in order:
import { useEffect } from 'react';
import Purchases from 'react-native-purchases';
import InfluTo from '@influto/react-native-sdk';
export default function App() {
useEffect(() => {
(async () => {
Purchases.configure({ apiKey: REVENUECAT_API_KEY }); // RevenueCat apps: configure it first
try {
await InfluTo.initialize({
apiKey: process.env.EXPO_PUBLIC_INFLUTO_API_KEY!,
appVersion: '1.4.0',
debug: __DEV__,
});
} catch (error) {
console.warn('InfluTo initialize failed', error); // retried on the next launch
return;
}
const attribution = await InfluTo.checkAttribution();
await InfluTo.identifyUser(await Purchases.getAppUserID());
})();
}, []);
// …
}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 in a gitignored .env read through Expo's EXPO_PUBLIC_ variables.
| Option | Type | Default | What it does |
|---|---|---|---|
apiKey |
string |
required | The app's SDK key (it_…) |
debug |
boolean |
false |
Logs [InfluTo] lines to the console |
appVersion |
string |
"unknown" |
Your app version, sent with the startup call |
autoCapture |
boolean |
true |
Store-direct apps: report purchases from expo-iap / react-native-iap automatically. Set false if you call reportPurchase yourself |
oneTimeProductIds |
string[] |
none | Your Android one-time and consumable product IDs, so captured purchases are validated as one-time purchases |
Detect referred users
const attribution = await InfluTo.checkAttribution();
if (attribution.attributed) {
// User came from a referral — show a special offer / extended trial.
// The SDK also sets the RevenueCat attributes influto_code + influto_referral.
console.log('Referred by', attribution.referralCode);
}| 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:
await InfluTo.identifyUser(await Purchases.getAppUserID());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 on every render.
Track events
await InfluTo.trackEvent({
eventType: 'paywall_viewed',
appUserId: userId,
properties: { placement: 'onboarding' },
});Events show in the Integration log (under Setup & SDK) 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 yourself.
Let users type a code
// Validate, then apply a code the user typed
const result = await InfluTo.applyCode('FITGURU30');
if (result.valid && result.applied) {
// Unlock the offer
}
// Or drop in the ready-made UI:
import { ReferralCodeInput } from '@influto/react-native-sdk/ui';
<ReferralCodeInput onApplied={(result) => {/* ... */}} />
// Free-access codes (INFLU-… or your comp codes): redeem with
// setReferralCode(code) after identifyUser (or pass appUserId) — grantsAccess
// === true means unlock premium now — and gate premium on (await InfluTo.checkAccess()).hasAccess.applyCode(code, appUserId?) validates the code and applies it if it's valid. Codes are trimmed and uppercased for you. An invalid code comes back with valid: false and an error_code: 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 ReferralCodeInput from @influto/react-native-sdk/ui:
| Prop | Default | Notes |
|---|---|---|
onApplied(result) |
— | The code was valid and applied |
onValidated(result) |
— | After every validation, valid or not |
onSkip() |
— | The skip button was pressed |
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 on mount |
validateOnBlur / showSkipButton |
true / true |
|
showCampaignName / showReferrerName |
false / false |
Show a creator's name only with their consent |
colors, fonts, labels, style |
— | Theming and translated labels |
More on the UX: Let users enter a referral code. All props: 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
With react-native-purchases installed, there's nothing to wire in code: when checkAttribution finds a referral, or a code is applied, the SDK sets influto_code (the code) and influto_referral (the string "true") on the RevenueCat subscriber. Then connect the webhook — see RevenueCat.
Store-direct
Install expo-iap or react-native-iap. When the app is set to store-direct in Settings → Validation and autoCapture isn't false, initialize starts listening for new purchases and back-fills existing ones. Each purchase is reported once; without an IAP library, capture quietly does nothing. You can also drive it yourself with enableAutoPurchaseCapture({ backSync, oneTimeProductIds }), disableAutoPurchaseCapture() and syncExistingPurchases().
To report purchases yourself instead, set autoCapture: false and call reportPurchase after each successful purchase:
// Store-direct only (no RevenueCat). With expo-iap or react-native-iap installed,
// purchases are captured automatically on store-direct apps. To report
// manually instead, pass the store-signed proof:
await InfluTo.reportPurchase({
platform: 'ios', // or 'android'
signedTransaction: jwsString, // iOS: StoreKit 2 JWS
// purchaseToken: token, // Android: Play Billing purchaseToken
});reportPurchasethrows on failure. The error message includes the HTTP status, for exampleAPI request failed: 503 - ….503: the exchange rate was briefly unavailable — retry later. Reports are idempotent.400: the platform isn't set to store-direct yet.409(Android one-time products): the purchase isn't in the PURCHASED state yet.- Android one-time and consumable products need
productId, pluspriceandcurrencyfrom the product details. Captured purchases only carry a price when the IAP library puts one on the purchase, and a one-time purchase without a price is recorded at $0 — report those yourself.
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) after identifyUser, or pass the user ID — a code with no user is refused. The result tells you what happened: grantsAccess, freeAccess, entitlement and expiresAt. applyCode doesn't return these flags; after it, result.campaign?.campaign_type === 'FREE_ACCESS' tells you the code was a free-access code.
Gate premium on your store entitlement or InfluTo's answer:
const premium = entitlementActive || (await InfluTo.checkAccess()).hasAccess;checkAccess(appUserId?) uses the identified user when you don't pass one, caches a positive answer for 5 minutes, never caches a negative one, and returns hasAccess: false on errors. See Free access codes.
Verify
After the first successful initialize, Setup & SDK shows "SDK connected" with the platform and SDK version it last saw. With debug: true the SDK logs each step, and the Integration log 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.
React Native notes
- AsyncStorage must be in your app's own dependencies, or its native module isn't linked.
- Order matters for RevenueCat: configure
react-native-purchases, theninitialize, thencheckAttribution. - Initialize once. Put it at the app root, not in a screen that mounts repeatedly, and don't call
identifyUserfrom a render path. - Expo Go runs the SDK, but not store libraries. Auto-capture does nothing there, which is safe.
- Android one-time products: list them in
oneTimeProductIds, and report them yourself if your IAP library doesn't expose the price.