Browse docsOverview
React Native SDK

React Native SDK

Reflect SDK for React Native — a thin JS + native bridge over the shared ReflectCore engine. Mobile attribution, event tracking, deep linking, and SKAN for iOS and Android.

Upgrade to v2.0.4 — every release through v2.0.2 shipped no privacy engine

Releases up to and including v2.0.2 pinned Android core com.github.bablu147:reflect-android:1.0.0, which contained no privacy engine at all — no transport gate, no fail-closed posture, no deletion journal, and no 90-day click-context retention. v2.0.4 pins reflect-android:1.1.2and resolves ReflectCore ~> 1.1 (currently 1.1.4) on iOS — the 1.1.x privacy line (1.1.0 is the floor; 1.1.1 was the first core tag that no longer brands iOS events as flutter on the wire). Upgrading is strongly recommended.

The package itself remains a thin wrapper over the shared native core — the same Kotlin (reflect-android) and Swift (reflect-ios) core the Flutter and Unity SDKs use. All logic (durable on-disk queue, HMAC-signed ingest, batching, response-driven retry, device signals, deferred deep links, attribution, SKAN, ATT) lives in the core; this package only translates the RN bridge onto it. v1 was a standalone re-implementation that posted unsigned events — v2 is a breaking native rewrite, but the public JS API stays backward-compatible and gains new methods. Your install_uuid is preserved across the upgrade, so it is not counted as a reinstall.

Installation

Install from GitHub, pinned to a tagged release — this package is not published to the public npm registry:

npm install github:bablu147/reflect-sdk-react-native#v2.0.4

This is an autolinked native module. On iOS, ReflectCore is not published on CocoaPods trunk, and CocoaPods will not follow this package's podspec dependency to a git source — so you must declare the pod explicitly in your ios/Podfile, pinned to the tag below:

pod 'ReflectCore', :git => 'https://github.com/bablu147/reflect-ios.git', :tag => '1.1.4'

Then install the pods:

cd ios && pod install

On Android, the shared core is pulled from JitPack. React Native 0.73+ resolves dependencies per-project, so add the repository to an allprojects block in your app's root android/build.gradle. A settings.gradle dependencyResolutionManagement block is ignored on RN 0.73+, so the build fails with Could not find com.github.bablu147:reflect-android:1.1.2. A native module cannot inject a repository into the consuming build, so this step is unavoidably yours:

// android/build.gradle — after the buildscript { } block
allprojects {
  repositories {
    maven { url 'https://jitpack.io' }   // com.github.bablu147:reflect-android
  }
}

Then rebuild the native app — a Metro/JS reload is not enough for a newly-added native module:

npx react-native run-android

The Android AAR brings its transitive dependencies (Play Install Referrer, App Set ID, ads-identifier) and R8 keep-rules automatically.

Requirements

  • React Native >=0.71.0
  • React >=18.0.0
  • iOS 13+, Android (Google Play Services)
  • Supports both Old Architecture (Bridge) and New Architecture (legacy bridge via the interop layer)
  • Pulls the shared native core — com.github.bablu147:reflect-android:1.1.2 from JitPack on Android, the ReflectCore pod (~> 1.1, currently 1.1.4) on iOS

Quick start

import { Reflect } from '@reflect-sdk/react-native';

// Initialize once at app startup
Reflect.initialize({
  appKey:     'app_live_...',
  companyKey: 'co_live_...',
  baseUrl:    'https://api.reflect.cloud',
  // signingSecret: 'sec_...',  // optional — HMAC-signs ingest + surfaces attribution
});

With a signingSecret set, every event batch is HMAC-signed and posted to the authenticated /event endpoint; without one the SDK falls back to the unsigned /event/batch path. baseUrl already defaults to https://api.reflect.cloud. An app_open event is sent automatically on init. Events from this release carry sdk_version react-native-2.0.4 on the wire — use it to confirm in the dashboard that your build picked up the upgrade.

API reference

MethodDescription
Reflect.initialize(config)Initialize the SDK
Reflect.trackEvent(name, props?)Track event with optional properties
Reflect.trackRevenue(params)Track revenue event
Reflect.trackPurchase(params)Track purchase with optional receipt data
Reflect.trackSubscription(params)Track subscription event
Reflect.verifyPurchase(params)Verify a receipt server-side and return a typed result
Reflect.trackAdRevenue(params)Track mediated ad revenue (fires ad_impression)
Reflect.setUserId(userId)Set user ID
Reflect.clearUserId()Clear user ID
Reflect.setEmail(email)Attach the user's RAW email to future events (server hashes it; consent-gated) — enables email attribution
Reflect.clearEmail()Clear the stored email (e.g. on logout)
Reflect.setUserProperties(props)Set user-level properties
Reflect.setGlobalProperty(key, val)Set a property merged into every event
Reflect.unsetGlobalProperty(key)Remove a global property
Reflect.clearGlobalProperties()Remove all global properties
Reflect.setPartnerParameter(key, val)Set a global partner parameter forwarded to integrations
Reflect.setPartnerSharing(map)Per-partner third-party-sharing overrides
Reflect.setThirdPartySharing(enabled)Opt in/out of partner data sharing
Reflect.setAdvertisingConsent(granted)Grant/revoke IDFA/GAID consent
Reflect.setExternalDeviceId(id)Attach a customer-owned device identifier
Reflect.requestIosTracking()Request iOS ATT permission (returns status)
Reflect.getInstallUuid()Get persistent install UUID
Reflect.getAttribution()Get cached attribution data
Reflect.getAttributionWithTimeout(ms)Force a fresh attribution check, resolving early or after the timeout
Reflect.onAttribution(listener)Subscribe to attribution changes (returns unsubscribe fn)
Reflect.updateConversionValue(...)Update SKAN CV (iOS only)
Reflect.getInitialDeepLink()Get the deep link that opened the app
Reflect.getLastDeepLink()The last deep link handled (cold/warm/deferred)
Reflect.onDeepLink(listener)Subscribe to deep link events (returns unsubscribe fn)
Reflect.resolveDeepLink(url)Unshorten/resolve a branded tracking URL via the server
Reflect.handleDeepLink(url)Feed the SDK a link the app captured itself
Reflect.setPushToken(token)Sticky push token stamped on every event
Reflect.setIntegrityToken(token)Supply a Play Integrity / App Attest token (anti-fraud)
Reflect.setAudience(...tags)Tag install for audience segmentation
Reflect.deleteUserData()GDPR — delete all user data
Reflect.setEnabled(enabled)Enable/disable at runtime
Reflect.isEnabled()Whether measurement is currently enabled
Reflect.setOfflineMode(offline)Pause/resume sending (keeps tracking + queuing on-device)
Reflect.flush()Force-flush buffered events

Event tracking

// Custom event
Reflect.trackEvent('level_complete', { level: 5, score: 12340 });

// Revenue
Reflect.trackRevenue({
  amount: 4.99,
  currency: 'USD',
  productId: 'gems_500',
  transactionId: 'txn_abc123',
});

// Attribute installs/revenue to an email (pass it RAW — the server hashes it;
// only sent while consent is not denied).
Reflect.setEmail('[email protected]');
Reflect.clearEmail();   // on logout

The SDK sends the event property bag on the wire as properties; the server maps it to its canonical props field, so props.email reaches attribution.

Deep linking

import { useEffect } from 'react';
import { Reflect } from '@reflect-sdk/react-native';

function App() {
  useEffect(() => {
    // Cold launch deep link (incl. server-resolved deferred links)
    Reflect.getInitialDeepLink().then(data => {
      if (data) navigate(data.path);
    });

    // Warm deep links
    const unsub = Reflect.onDeepLink(data => {
      navigate(data.path);
    });

    return unsub;
  }, []);
}

Android captures warm (onNewIntent) and cold-launch links automatically. On iOS, forward the URL callbacks from your AppDelegate into the module (ReflectModule.stashLaunchURL: / handleURL:) — see the package README for the exact hooks.

SKAN (iOS)

const result = await Reflect.updateConversionValue(42, 'high', false);
if (!result.success) console.warn('SKAN error:', result.error);

Standard events

Pre-built helpers with typed parameters:

import { signUpWith, levelCompleted, addedToCart, adShown } from '@reflect-sdk/react-native';

signUpWith('google');
levelCompleted(5, 12340);
addedToCart('gems_500', 4.99, 'USD');
adShown('admob', 'interstitial', 0.02, 'USD');

Global properties & audience

Reflect.setGlobalProperty('ab_group', 'variant_b');
Reflect.setAudience('whale', 'early_adopter');

Purchase & subscription

Reflect.trackPurchase({
  productId: 'gems_500',
  price: 4.99,
  currency: 'USD',
  receiptData: '<base64>',
});

Reflect.trackSubscription({
  productId: 'pro_monthly',
  price: 9.99,
  currency: 'USD',
  isTrial: true,
});

// Verify a receipt server-side without tracking (returns a typed result)
const result = await Reflect.verifyPurchase({
  productId: 'gems_500',
  transactionId: 'txn_abc123',
  receiptData: '<base64>',
});

Privacy (GDPR)

const deleted = await Reflect.deleteUserData();
// Clears local state + sends server deletion request
v2.0.3 — shared native core, signed ingest
Running on the shared ReflectCore, the SDK HMAC-signs ingest, and gains server-side receipt verification (verifyPurchase), mediated ad revenue (trackAdRevenue), partner parameters & sharing controls, ATT (requestIosTracking), attestation tokens (setIntegrityToken), link resolution (resolveDeepLink/handleDeepLink), and offline-mode / enable toggles — on top of the existing v1 track, consent, deep-link, and SKAN API.