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.
ReflectCore engine — 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.0This is an autolinked native module. On iOS, install the pods — this pulls the shared ReflectCore pod:
cd ios && pod installOn 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+ and the build will fail to resolve the core:
// 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-androidThe Android AAR brings its transitive dependencies (Play Install Referrer, App Set ID, ads-identifier) and R8 keep-rules automatically. If the ReflectCore pod is not yet on CocoaPods trunk, add it to your ios/Podfile:
pod 'ReflectCore', :git => 'https://github.com/bablu147/reflect-ios.git', :tag => '1.0.0'Requirements
- React Native >=0.71.0
- React >=18.0.0
- iOS 12+, Android (Google Play Services)
- Supports both Old Architecture (Bridge) and New Architecture (legacy bridge via the interop layer)
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. An app_open event is sent automatically on init.
API reference
| Method | Description |
|---|---|
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('user@example.com');
Reflect.clearEmail(); // on logoutThe 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 requestReflectCore, the SDK now 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.