@datalyr/react-native 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,20 +12,38 @@ Mobile analytics and attribution SDK for React Native and Expo. Track events, id
12
12
  - [Custom Events](#custom-events)
13
13
  - [Screen Views](#screen-views)
14
14
  - [E-Commerce Events](#e-commerce-events)
15
+ - [Revenue Events](#revenue-events)
15
16
  - [User Identity](#user-identity)
16
17
  - [Anonymous ID](#anonymous-id)
17
18
  - [Identifying Users](#identifying-users)
19
+ - [Alias](#alias)
18
20
  - [User Properties](#user-properties)
21
+ - [Logout](#logout)
22
+ - [Sessions](#sessions)
19
23
  - [Attribution](#attribution)
20
24
  - [Automatic Capture](#automatic-capture)
25
+ - [Manual Attribution](#manual-attribution)
21
26
  - [Web-to-App Attribution](#web-to-app-attribution)
27
+ - [Deferred Attribution](#deferred-attribution)
28
+ - [Customer Journey](#customer-journey)
22
29
  - [Event Queue](#event-queue)
23
30
  - [Auto Events](#auto-events)
24
31
  - [SKAdNetwork](#skadnetwork)
25
32
  - [Platform Integrations](#platform-integrations)
33
+ - [Meta (Facebook)](#meta-facebook)
34
+ - [TikTok](#tiktok)
35
+ - [Google Ads](#google-ads)
26
36
  - [Apple Search Ads](#apple-search-ads)
37
+ - [Google Play Install Referrer](#google-play-install-referrer)
38
+ - [App Tracking Transparency](#app-tracking-transparency)
39
+ - [Enhanced App Campaigns](#enhanced-app-campaigns)
40
+ - [Third-Party Integrations](#third-party-integrations)
41
+ - [Superwall](#superwall)
42
+ - [RevenueCat](#revenuecat)
43
+ - [Migrating from AppsFlyer / Adjust](#migrating-from-appsflyer--adjust)
27
44
  - [Expo Support](#expo-support)
28
45
  - [TypeScript](#typescript)
46
+ - [API Reference](#api-reference)
29
47
  - [Troubleshooting](#troubleshooting)
30
48
  - [License](#license)
31
49
 
@@ -118,24 +136,62 @@ Every event includes:
118
136
 
119
137
  ## Configuration
120
138
 
139
+ All fields except `apiKey` are optional.
140
+
121
141
  ```typescript
122
142
  await Datalyr.initialize({
123
143
  // Required
124
- apiKey: string,
144
+ apiKey: string, // API key from dashboard (starts with 'dk_')
145
+
146
+ // Optional: workspace
147
+ workspaceId?: string, // Workspace ID for multi-workspace setups
148
+
149
+ // Debugging
150
+ debug?: boolean, // Console logging (default: false)
151
+
152
+ // Network
153
+ endpoint?: string, // API endpoint URL (default: 'https://api.datalyr.com')
154
+ useServerTracking?: boolean, // Use server-side tracking (default: true)
155
+ maxRetries?: number, // Max retry attempts for failed requests (default: 3)
156
+ retryDelay?: number, // Delay between retries in ms (default: 1000)
157
+ timeout?: number, // Request timeout in ms (default: 15000)
125
158
 
126
159
  // Features
127
- debug?: boolean, // Console logging
128
- enableAutoEvents?: boolean, // Track app lifecycle
129
- enableAttribution?: boolean, // Capture attribution data
160
+ enableAutoEvents?: boolean, // Track app lifecycle automatically (default: true)
161
+ enableAttribution?: boolean, // Capture attribution data (default: true)
162
+ enableWebToAppAttribution?: boolean, // Web-to-app attribution matching (default: true)
163
+
164
+ // Event queue
165
+ batchSize?: number, // Events per batch (default: 10)
166
+ flushInterval?: number, // Auto-flush interval in ms (default: 30000)
167
+ maxQueueSize?: number, // Max queued events (default: 100)
130
168
 
131
- // Event Queue
132
- batchSize?: number, // Events per batch (default: 10)
133
- flushInterval?: number, // Send interval ms (default: 30000)
134
- maxQueueSize?: number, // Max queued events (default: 100)
169
+ // Auto events
170
+ autoEventConfig?: AutoEventConfig, // Fine-grained auto-event settings (see below)
135
171
 
136
172
  // iOS
137
- skadTemplate?: 'ecommerce' | 'gaming' | 'subscription',
173
+ skadTemplate?: 'ecommerce' | 'gaming' | 'subscription', // SKAdNetwork conversion template
174
+ });
175
+ ```
176
+
177
+ ### AutoEventConfig
178
+
179
+ ```typescript
180
+ interface AutoEventConfig {
181
+ trackSessions?: boolean; // Track session_start / session_end
182
+ trackScreenViews?: boolean; // Track screen views automatically
183
+ trackAppUpdates?: boolean; // Track app_update events
184
+ trackPerformance?: boolean; // Track performance metrics
185
+ sessionTimeoutMs?: number; // Session timeout in ms
186
+ }
187
+ ```
138
188
 
189
+ Update at runtime:
190
+
191
+ ```typescript
192
+ Datalyr.updateAutoEventsConfig({
193
+ trackSessions: true,
194
+ sessionTimeoutMs: 1800000, // 30 minutes
139
195
  });
140
196
  ```
141
197
 
@@ -145,8 +201,6 @@ await Datalyr.initialize({
145
201
 
146
202
  ### Custom Events
147
203
 
148
- Track any action in your app:
149
-
150
204
  ```typescript
151
205
  // Simple event
152
206
  await Datalyr.track('signup_started');
@@ -170,8 +224,6 @@ await Datalyr.track('level_completed', {
170
224
 
171
225
  ### Screen Views
172
226
 
173
- Track navigation:
174
-
175
227
  ```typescript
176
228
  await Datalyr.screen('Home');
177
229
 
@@ -214,6 +266,26 @@ await Datalyr.trackLead(100.0, 'USD');
214
266
  await Datalyr.trackAddPaymentInfo(true);
215
267
  ```
216
268
 
269
+ ### Revenue Events
270
+
271
+ Track revenue with automatic SKAdNetwork encoding:
272
+
273
+ ```typescript
274
+ await Datalyr.trackRevenue('in_app_purchase', {
275
+ value: 4.99,
276
+ currency: 'USD',
277
+ product_id: 'gems_500',
278
+ });
279
+ ```
280
+
281
+ ### App Update Tracking
282
+
283
+ Manually track version changes:
284
+
285
+ ```typescript
286
+ await Datalyr.trackAppUpdate('1.0.0', '1.1.0');
287
+ ```
288
+
217
289
  ---
218
290
 
219
291
  ## User Identity
@@ -246,6 +318,18 @@ After `identify()`:
246
318
  - All future events include `user_id`
247
319
  - Historical anonymous events can be linked server-side
248
320
 
321
+ ### Alias
322
+
323
+ Associate a new user ID with a previous one. Use this when a user's identity changes (e.g., after account merge):
324
+
325
+ ```typescript
326
+ // Link new ID to the currently identified user
327
+ await Datalyr.alias('new_user_456');
328
+
329
+ // Or specify the previous ID explicitly
330
+ await Datalyr.alias('new_user_456', 'old_user_123');
331
+ ```
332
+
249
333
  ### User Properties
250
334
 
251
335
  Pass any user attributes:
@@ -279,6 +363,20 @@ This:
279
363
 
280
364
  ---
281
365
 
366
+ ## Sessions
367
+
368
+ ```typescript
369
+ // Get current session data
370
+ const session = Datalyr.getCurrentSession();
371
+
372
+ // Force end the current session
373
+ await Datalyr.endSession();
374
+ ```
375
+
376
+ Sessions are managed automatically when `enableAutoEvents` is enabled. A new session starts on app launch, and the current session ends after 30 minutes of inactivity (configurable via `autoEventConfig.sessionTimeoutMs`).
377
+
378
+ ---
379
+
282
380
  ## Attribution
283
381
 
284
382
  ### Automatic Capture
@@ -297,13 +395,24 @@ Captured parameters:
297
395
  | Click IDs | `fbclid`, `gclid`, `ttclid`, `twclid`, `li_click_id`, `msclkid` |
298
396
  | Campaign | `campaign_id`, `adset_id`, `ad_id` |
299
397
 
398
+ ### Manual Attribution
399
+
400
+ Set attribution programmatically:
401
+
402
+ ```typescript
403
+ await Datalyr.setAttributionData({
404
+ utm_source: 'newsletter',
405
+ utm_campaign: 'spring_sale',
406
+ });
407
+ ```
408
+
300
409
  ### Web-to-App Attribution
301
410
 
302
411
  Automatically recover attribution from a web prelander when users install the app from an ad.
303
412
 
304
413
  **How it works:**
305
- - **Android**: Attribution params are passed through the Play Store `referrer` URL parameter (set by the web SDK's `trackAppDownloadClick()`). The mobile SDK reads these via the Play Install Referrer API deterministic, ~95% accuracy.
306
- - **iOS**: On first install, the SDK calls the Datalyr API to match the device's IP against recent `$app_download_click` web events within 24 hours ~90%+ accuracy for immediate installs.
414
+ - **Android**: Attribution params are passed through the Play Store `referrer` URL parameter (set by the web SDK's `trackAppDownloadClick()`). The mobile SDK reads these via the Play Install Referrer API -- deterministic, ~95% accuracy.
415
+ - **iOS**: On first install, the SDK calls the Datalyr API to match the device's IP against recent `$app_download_click` web events within 24 hours -- ~90%+ accuracy for immediate installs.
307
416
 
308
417
  No additional mobile code is needed. Attribution is recovered automatically during `initialize()` on first install, before the `app_install` event fires.
309
418
 
@@ -314,15 +423,43 @@ After a match, the SDK:
314
423
 
315
424
  **Fallback:** If IP matching misses (e.g., VPN toggle during install), email-based attribution is still recovered when `identify()` is called with the user's email.
316
425
 
317
- ### Manual Attribution
426
+ ### Deferred Attribution
318
427
 
319
- Set attribution programmatically:
428
+ Retrieve deferred attribution data captured from deep links or install referrers:
320
429
 
321
430
  ```typescript
322
- await Datalyr.setAttributionData({
323
- utm_source: 'newsletter',
324
- utm_campaign: 'spring_sale',
325
- });
431
+ const deferred = Datalyr.getDeferredAttributionData();
432
+ if (deferred) {
433
+ console.log(deferred.url); // Deep link URL
434
+ console.log(deferred.source); // Attribution source
435
+ console.log(deferred.fbclid); // Facebook click ID
436
+ console.log(deferred.gclid); // Google click ID
437
+ console.log(deferred.ttclid); // TikTok click ID
438
+ console.log(deferred.utmSource); // UTM source
439
+ console.log(deferred.utmMedium); // UTM medium
440
+ console.log(deferred.utmCampaign); // UTM campaign
441
+ console.log(deferred.utmContent); // UTM content
442
+ console.log(deferred.utmTerm); // UTM term
443
+ console.log(deferred.campaignId); // Campaign ID
444
+ console.log(deferred.adsetId); // Adset ID
445
+ console.log(deferred.adId); // Ad ID
446
+ }
447
+ ```
448
+
449
+ ---
450
+
451
+ ## Customer Journey
452
+
453
+ Access multi-touch attribution journey data via the `datalyr` singleton instance:
454
+
455
+ ```typescript
456
+ import { datalyr } from '@datalyr/react-native';
457
+
458
+ // Summary: first/last touch, touchpoint count
459
+ const summary = datalyr.getJourneySummary();
460
+
461
+ // Full journey: all touchpoints in order
462
+ const journey = datalyr.getJourney();
326
463
  ```
327
464
 
328
465
  ---
@@ -400,7 +537,10 @@ await Datalyr.initialize({
400
537
  skadTemplate: 'ecommerce',
401
538
  });
402
539
 
403
- // E-commerce events update conversion values
540
+ // Track with automatic SKAN conversion value encoding
541
+ await Datalyr.trackWithSKAdNetwork('purchase', { value: 99.99 });
542
+
543
+ // Or use e-commerce helpers (also update SKAN automatically)
404
544
  await Datalyr.trackPurchase(99.99, 'USD');
405
545
  ```
406
546
 
@@ -410,11 +550,65 @@ await Datalyr.trackPurchase(99.99, 'USD');
410
550
  | `gaming` | level_complete, tutorial_complete, purchase, achievement_unlocked |
411
551
  | `subscription` | trial_start, subscribe, upgrade, cancel, signup |
412
552
 
553
+ ### Test Conversion Values
554
+
555
+ Preview the conversion value an event would produce without sending it to Apple:
556
+
557
+ ```typescript
558
+ const value = Datalyr.getConversionValue('purchase', { value: 49.99 });
559
+ // Returns a number (0-63) or null if no template is configured
560
+ ```
561
+
413
562
  ---
414
563
 
415
564
  ## Platform Integrations
416
565
 
417
- Conversion event routing to Meta, TikTok, and Google is handled server-side via the postback system. No client-side SDK configuration is needed for these platforms.
566
+ Conversion events are routed to ad platforms server-side via the Datalyr postback system. No client-side ad SDKs (Facebook SDK, TikTok SDK, etc.) are needed in your app. The SDK captures click IDs and attribution data from ad URLs, then the backend handles hashing, formatting, and sending conversions to each platform's API.
567
+
568
+ ### Meta (Facebook)
569
+
570
+ Conversions are sent to Meta via the [Conversions API (CAPI)](https://developers.facebook.com/docs/marketing-api/conversions-api/).
571
+
572
+ **What the SDK does:** Captures `fbclid` from ad click URLs, collects IDFA (when ATT authorized on iOS), and sends user data (email, phone) with events.
573
+
574
+ **What the backend does:** Hashes PII (SHA-256), formats the CAPI payload, and sends conversions with the `fbclid` and `_fbc`/`_fbp` cookies for matching.
575
+
576
+ **Setup:**
577
+ 1. Connect your Meta ad account in the Datalyr dashboard (Settings > Connections)
578
+ 2. Select your Meta Pixel
579
+ 3. Create postback rules to map events (e.g., `purchase` -> `Purchase`, `lead` -> `Lead`)
580
+
581
+ No Facebook SDK needed in your app. No `Info.plist` changes, no `FacebookAppID`.
582
+
583
+ ### TikTok
584
+
585
+ Conversions are sent to TikTok via the [Events API](https://business-api.tiktok.com/portal/docs?id=1741601162187777).
586
+
587
+ **What the SDK does:** Captures `ttclid` from ad click URLs and collects device identifiers (IDFA on iOS, GAID on Android).
588
+
589
+ **What the backend does:** Hashes user data, formats the Events API payload, and sends conversions with the `ttclid` and `_ttp` cookie for matching.
590
+
591
+ **Setup:**
592
+ 1. Connect your TikTok Ads account in the Datalyr dashboard (Settings > Connections)
593
+ 2. Select your TikTok Pixel
594
+ 3. Create postback rules to map events (e.g., `purchase` -> `CompletePayment`, `add_to_cart` -> `AddToCart`)
595
+
596
+ No TikTok SDK needed in your app. No access tokens, no native configuration.
597
+
598
+ ### Google Ads
599
+
600
+ Conversions are sent to Google via the [Google Ads API](https://developers.google.com/google-ads/api/docs/conversions/overview).
601
+
602
+ **What the SDK does:** Captures `gclid`, `gbraid`, and `wbraid` from ad click URLs. Collects user data for enhanced conversions.
603
+
604
+ **What the backend does:** Hashes user data, maps events to Google conversion actions, and sends conversions with click IDs for attribution.
605
+
606
+ **Setup:**
607
+ 1. Connect your Google Ads account in the Datalyr dashboard (Settings > Connections)
608
+ 2. Select your conversion actions
609
+ 3. Create postback rules to map events (e.g., `purchase` -> your Google conversion action)
610
+
611
+ No Google SDK needed in your app beyond the Play Install Referrer (already included for Android).
418
612
 
419
613
  ### Apple Search Ads
420
614
 
@@ -442,6 +636,52 @@ Attribution data is automatically included in all events with the `asa_` prefix:
442
636
 
443
637
  No additional configuration needed. The SDK uses Apple's AdServices API.
444
638
 
639
+ ### Google Play Install Referrer
640
+
641
+ Android-only. Captures UTM parameters and click IDs from the Google Play Store install referrer URL. This data is retrieved automatically on first launch via the Play Install Referrer API.
642
+
643
+ **How it works:**
644
+ 1. User clicks an ad or link with UTM parameters
645
+ 2. Google Play Store stores the referrer URL
646
+ 3. On first app launch, the SDK retrieves the referrer
647
+ 4. Attribution data (utm_source, utm_medium, gclid, etc.) is extracted and merged into the session
648
+
649
+ **Access the raw referrer data:**
650
+
651
+ ```typescript
652
+ import { datalyr } from '@datalyr/react-native';
653
+
654
+ const referrer = datalyr.getPlayInstallReferrer();
655
+ if (referrer) {
656
+ // Google Ads click IDs
657
+ console.log(referrer.gclid); // Standard Google Ads click ID
658
+ console.log(referrer.gbraid); // Privacy-safe click ID (iOS App campaigns)
659
+ console.log(referrer.wbraid); // Privacy-safe click ID (Web-to-App campaigns)
660
+
661
+ // UTM parameters
662
+ console.log(referrer.utmSource);
663
+ console.log(referrer.utmMedium);
664
+ console.log(referrer.utmCampaign);
665
+ console.log(referrer.utmTerm);
666
+ console.log(referrer.utmContent);
667
+
668
+ // Timestamps
669
+ console.log(referrer.referrerClickTimestamp); // When the referrer link was clicked (ms)
670
+ console.log(referrer.installBeginTimestamp); // When the install began (ms)
671
+ console.log(referrer.installCompleteTimestamp); // When the install completed (ms)
672
+
673
+ // Raw referrer URL
674
+ console.log(referrer.referrerUrl);
675
+ }
676
+ ```
677
+
678
+ **Requirements:**
679
+ - Android only (returns `null` on iOS)
680
+ - Requires the Google Play Install Referrer Library in `android/app/build.gradle`:
681
+ ```groovy
682
+ implementation 'com.android.installreferrer:installreferrer:2.2'
683
+ ```
684
+
445
685
  ### App Tracking Transparency
446
686
 
447
687
  Update after ATT dialog:
@@ -451,37 +691,219 @@ const { status } = await requestTrackingPermissionsAsync();
451
691
  await Datalyr.updateTrackingAuthorization(status === 'granted');
452
692
  ```
453
693
 
454
- ### Check Status
694
+ ### Integration Status
455
695
 
456
696
  ```typescript
457
697
  const status = Datalyr.getPlatformIntegrationStatus();
458
- // { appleSearchAds: true }
698
+ // { appleSearchAds: boolean, playInstallReferrer: boolean }
459
699
  ```
460
700
 
461
701
  ---
462
702
 
463
- ## Expo Support
703
+ ## Enhanced App Campaigns
704
+
705
+ Run mobile app ads through web campaigns (Meta Sales, TikTok Traffic, Google Ads) that redirect users to the app store through your own domain. This bypasses SKAN restrictions, ATT requirements, and adset limits -- ad platforms treat these as regular web campaigns.
706
+
707
+ ### How It Works
708
+
709
+ 1. User clicks your ad -> lands on a page on your domain with the Datalyr web SDK (`dl.js`)
710
+ 2. SDK captures attribution (fbclid, ttclid, gclid, UTMs, ad cookies like `_fbp`/`_fbc`/`_ttp`)
711
+ 3. User redirects to app store (via button click or auto-redirect)
712
+ 4. User installs app -> mobile SDK matches via Play Store referrer (Android, ~95%) or IP matching (iOS, ~90%+)
713
+ 5. In-app events fire -> conversions sent to Meta/TikTok/Google server-side via postbacks
714
+
715
+ ### Setup
716
+
717
+ **1. Create a tracking link** in the Datalyr dashboard: Track -> Create Link -> App Link. Enter your prelander page URL and app store URLs.
718
+
719
+ **2. Host a page on your domain** with one of these options:
720
+
721
+ #### Option A: Prelander (Recommended)
722
+
723
+ A real landing page with a download button. Better ad platform compliance, higher intent.
724
+
725
+ ```html
726
+ <!DOCTYPE html>
727
+ <html>
728
+ <head>
729
+ <meta charset="utf-8">
730
+ <meta name="viewport" content="width=device-width, initial-scale=1">
731
+ <title>Download Your App</title>
732
+ <script src="https://cdn.datalyr.com/dl.js" data-workspace="YOUR_WORKSPACE_ID"></script>
733
+ </head>
734
+ <body>
735
+ <h1>Download Our App</h1>
736
+ <button id="ios-download">Download for iOS</button>
737
+ <button id="android-download">Download for Android</button>
738
+
739
+ <script>
740
+ document.getElementById('ios-download').addEventListener('click', function() {
741
+ Datalyr.trackAppDownloadClick({
742
+ targetPlatform: 'ios',
743
+ appStoreUrl: 'https://apps.apple.com/app/idXXXXXXXXXX'
744
+ });
745
+ });
746
+ document.getElementById('android-download').addEventListener('click', function() {
747
+ Datalyr.trackAppDownloadClick({
748
+ targetPlatform: 'android',
749
+ appStoreUrl: 'https://play.google.com/store/apps/details?id=com.example.app'
750
+ });
751
+ });
752
+ </script>
753
+ </body>
754
+ </html>
755
+ ```
464
756
 
465
- ```typescript
466
- import { Datalyr } from '@datalyr/react-native/expo';
757
+ #### Option B: Redirect Page
758
+
759
+ Instant redirect -- no visible content, user goes straight to app store.
760
+
761
+ > **Note:** Some ad platforms (particularly Meta) may flag redirect pages with no visible content as low-quality landing pages or cloaking. Use the prelander option if compliance is a concern.
762
+
763
+ ```html
764
+ <!DOCTYPE html>
765
+ <html>
766
+ <head>
767
+ <script src="https://cdn.datalyr.com/dl.js" data-workspace="YOUR_WORKSPACE_ID"></script>
768
+ <script>
769
+ window.addEventListener('DOMContentLoaded', function() {
770
+ var isAndroid = /android/i.test(navigator.userAgent);
771
+ Datalyr.trackAppDownloadClick({
772
+ targetPlatform: isAndroid ? 'android' : 'ios',
773
+ appStoreUrl: isAndroid
774
+ ? 'https://play.google.com/store/apps/details?id=com.example.app'
775
+ : 'https://apps.apple.com/app/idXXXXXXXXXX'
776
+ });
777
+ });
778
+ </script>
779
+ </head>
780
+ <body></body>
781
+ </html>
467
782
  ```
468
783
 
469
- Same API as standard React Native.
784
+ **3. Set up your ad campaign:**
785
+
786
+ - **Meta Ads**: Campaign objective -> Sales, conversion location -> Website, placements -> Mobile only. Paste your page URL as the Website URL. No SKAN, no ATT, no adset limits.
787
+ - **TikTok Ads**: Campaign objective -> Website Conversions, paste your page URL as destination. Select your TikTok Pixel from Datalyr.
788
+ - **Google Ads**: Performance Max or Search campaign. Use your page URL as the landing page.
789
+
790
+ Add UTM parameters to the URL so attribution flows through:
791
+ - Meta: `?utm_source=facebook&utm_medium=cpc&utm_campaign={{campaign.name}}&utm_content={{adset.name}}&utm_term={{ad.name}}`
792
+ - TikTok: `?utm_source=tiktok&utm_medium=cpc&utm_campaign=__CAMPAIGN_NAME__&utm_content=__AID_NAME__&utm_term=__CID_NAME__`
793
+ - Google: `?utm_source=google&utm_medium=cpc&utm_campaign={campaignid}&utm_content={adgroupid}&utm_term={keyword}`
794
+
795
+ ### Important
796
+
797
+ - The page **must load JavaScript**. Server-side redirects (301/302, nginx, Cloudflare Page Rules) will NOT work.
798
+ - Host on your own domain -- do not use `datalyr.com` or shared domains.
799
+ - The redirect page adds ~100-200ms for the SDK to load. Prelander has no latency since the user clicks a button.
470
800
 
471
801
  ---
472
802
 
473
- ## TypeScript
803
+ ## Third-Party Integrations
804
+
805
+ ### Superwall
806
+
807
+ Pass Datalyr attribution data to Superwall to personalize paywalls by ad source, campaign, ad set, and keyword.
474
808
 
475
809
  ```typescript
476
- import {
477
- Datalyr,
478
- DatalyrConfig,
479
- EventData,
480
- UserProperties,
481
- AttributionData,
482
- } from '@datalyr/react-native';
810
+ import { Datalyr } from '@datalyr/react-native';
811
+ import Superwall from '@superwall/react-native-superwall';
812
+
813
+ // After both SDKs are initialized
814
+ Superwall.setUserAttributes(Datalyr.getSuperwallAttributes());
815
+
816
+ // Your placements will now have attribution data available as filters
817
+ Superwall.register({ placement: 'onboarding_paywall' });
818
+ ```
819
+
820
+ Call after `Datalyr.initialize()` completes. If using ATT on iOS, call again after the user responds to the ATT prompt to include the IDFA.
821
+
822
+ **Returned attribute keys:**
823
+
824
+ | Key | Description |
825
+ |-----|-------------|
826
+ | `datalyr_id` | The user's DATALYR visitor ID |
827
+ | `media_source` | Traffic source (e.g., `facebook`, `google`) |
828
+ | `campaign` | Campaign name from the ad |
829
+ | `adgroup` | Ad group or ad set name |
830
+ | `ad` | Individual ad ID |
831
+ | `keyword` | Search keyword that triggered the ad |
832
+ | `network` | Ad network name |
833
+ | `utm_source` | UTM source parameter |
834
+ | `utm_medium` | UTM medium parameter (e.g., `cpc`) |
835
+ | `utm_campaign` | UTM campaign parameter |
836
+ | `utm_term` | UTM term parameter |
837
+ | `utm_content` | UTM content parameter |
838
+ | `lyr` | DATALYR tracking link ID |
839
+ | `fbclid` | Meta click ID from the ad URL |
840
+ | `gclid` | Google click ID from the ad URL |
841
+ | `ttclid` | TikTok click ID from the ad URL |
842
+ | `idfa` | Apple advertising ID (only if ATT authorized) |
843
+ | `gaid` | Google advertising ID (Android) |
844
+ | `att_status` | App Tracking Transparency status (`0`-`3`) |
845
+
846
+ Only non-empty values are included.
847
+
848
+ ### RevenueCat
849
+
850
+ Pass Datalyr attribution data to RevenueCat for revenue attribution and offering targeting.
851
+
852
+ ```typescript
853
+ import { Datalyr } from '@datalyr/react-native';
854
+ import Purchases from 'react-native-purchases';
855
+
856
+ // After both SDKs are configured
857
+ Purchases.setAttributes(Datalyr.getRevenueCatAttributes());
483
858
  ```
484
859
 
860
+ Call after configuring the Purchases SDK and before the first purchase. If using ATT, call again after permission is granted to include IDFA.
861
+
862
+ **Reserved attributes (`$`-prefixed):**
863
+
864
+ | Key | Description |
865
+ |-----|-------------|
866
+ | `$datalyrId` | The user's DATALYR visitor ID |
867
+ | `$mediaSource` | Traffic source (e.g., `facebook`, `google`) |
868
+ | `$campaign` | Campaign name from the ad |
869
+ | `$adGroup` | Ad group or ad set name |
870
+ | `$ad` | Individual ad ID |
871
+ | `$keyword` | Search keyword that triggered the ad |
872
+ | `$idfa` | Apple advertising ID (only if ATT authorized) |
873
+ | `$gpsAdId` | Google advertising ID (Android) |
874
+ | `$attConsentStatus` | ATT consent status (see mapping below) |
875
+
876
+ **ATT status mapping for `$attConsentStatus`:**
877
+
878
+ | ATT Value | String |
879
+ |-----------|--------|
880
+ | 0 | `notDetermined` |
881
+ | 1 | `restricted` |
882
+ | 2 | `denied` |
883
+ | 3 | `authorized` |
884
+
885
+ **Custom attributes:**
886
+
887
+ | Key | Description |
888
+ |-----|-------------|
889
+ | `utm_source` | UTM source parameter |
890
+ | `utm_medium` | UTM medium parameter (e.g., `cpc`) |
891
+ | `utm_campaign` | UTM campaign parameter |
892
+ | `utm_term` | UTM term parameter |
893
+ | `utm_content` | UTM content parameter |
894
+ | `lyr` | DATALYR tracking link ID |
895
+ | `fbclid` | Meta click ID from the ad URL |
896
+ | `gclid` | Google click ID from the ad URL |
897
+ | `ttclid` | TikTok click ID from the ad URL |
898
+ | `wbraid` | Google web-to-app click ID |
899
+ | `gbraid` | Google app click ID |
900
+ | `network` | Ad network |
901
+ | `creative_id` | Creative ID |
902
+
903
+ Only non-empty values are included.
904
+
905
+ > Datalyr also receives Superwall and RevenueCat events via server-side webhooks for analytics. The SDK methods and webhook integration are independent -- you can use one or both.
906
+
485
907
  ---
486
908
 
487
909
  ## Migrating from AppsFlyer / Adjust
@@ -535,6 +957,135 @@ await Datalyr.trackPurchase(99.99, 'USD');
535
957
 
536
958
  ---
537
959
 
960
+ ## Expo Support
961
+
962
+ ```typescript
963
+ import { Datalyr } from '@datalyr/react-native/expo';
964
+ ```
965
+
966
+ Same API as standard React Native.
967
+
968
+ ---
969
+
970
+ ## TypeScript
971
+
972
+ ```typescript
973
+ import {
974
+ Datalyr,
975
+ DatalyrConfig,
976
+ EventData,
977
+ UserProperties,
978
+ AttributionData,
979
+ AutoEventConfig,
980
+ DeferredDeepLinkResult,
981
+ } from '@datalyr/react-native';
982
+ ```
983
+
984
+ ---
985
+
986
+ ## API Reference
987
+
988
+ All methods are static on the `Datalyr` class unless noted otherwise.
989
+
990
+ ### Initialization
991
+
992
+ | Method | Description |
993
+ |--------|-------------|
994
+ | `initialize(config: DatalyrConfig)` | Initialize the SDK. Must be called before any other method. |
995
+
996
+ ### Event Tracking
997
+
998
+ | Method | Description |
999
+ |--------|-------------|
1000
+ | `track(eventName, eventData?)` | Track a custom event |
1001
+ | `screen(screenName, properties?)` | Track a screen view |
1002
+ | `trackWithSKAdNetwork(event, properties?)` | Track event with SKAN conversion value encoding |
1003
+ | `trackPurchase(value, currency?, productId?)` | Track a purchase |
1004
+ | `trackSubscription(value, currency?, plan?)` | Track a subscription |
1005
+ | `trackRevenue(eventName, properties?)` | Track a revenue event |
1006
+ | `trackAddToCart(value, currency?, productId?, productName?)` | Track add-to-cart |
1007
+ | `trackViewContent(contentId?, contentName?, contentType?, value?, currency?)` | Track content view |
1008
+ | `trackInitiateCheckout(value, currency?, numItems?, productIds?)` | Track checkout start |
1009
+ | `trackCompleteRegistration(method?)` | Track registration |
1010
+ | `trackSearch(query, resultIds?)` | Track a search |
1011
+ | `trackLead(value?, currency?)` | Track a lead |
1012
+ | `trackAddPaymentInfo(success?)` | Track payment info added |
1013
+ | `trackAppUpdate(previousVersion, currentVersion)` | Track an app version update |
1014
+
1015
+ ### User Identity
1016
+
1017
+ | Method | Description |
1018
+ |--------|-------------|
1019
+ | `identify(userId, properties?)` | Identify a user |
1020
+ | `alias(newUserId, previousId?)` | Associate a new user ID with a previous one |
1021
+ | `reset()` | Clear user ID and start new session |
1022
+ | `getAnonymousId()` | Get the persistent anonymous device ID |
1023
+
1024
+ ### Sessions
1025
+
1026
+ | Method | Description |
1027
+ |--------|-------------|
1028
+ | `getCurrentSession()` | Get current session data |
1029
+ | `endSession()` | Force end the current session |
1030
+
1031
+ ### Attribution
1032
+
1033
+ | Method | Description |
1034
+ |--------|-------------|
1035
+ | `getAttributionData()` | Get captured attribution data |
1036
+ | `setAttributionData(data)` | Set attribution data manually |
1037
+ | `getDeferredAttributionData()` | Get deferred attribution from deep links / install referrer |
1038
+
1039
+ ### Configuration
1040
+
1041
+ | Method | Description |
1042
+ |--------|-------------|
1043
+ | `updateAutoEventsConfig(config)` | Update auto-event settings at runtime |
1044
+
1045
+ ### Platform Integrations
1046
+
1047
+ | Method | Description |
1048
+ |--------|-------------|
1049
+ | `getAppleSearchAdsAttribution()` | Get Apple Search Ads attribution (iOS) |
1050
+ | `getPlatformIntegrationStatus()` | Check which platform integrations are active |
1051
+ | `updateTrackingAuthorization(enabled)` | Update ATT status after user responds to dialog |
1052
+
1053
+ ### SKAdNetwork
1054
+
1055
+ | Method | Description |
1056
+ |--------|-------------|
1057
+ | `getConversionValue(event, properties?)` | Preview conversion value without sending to Apple |
1058
+
1059
+ ### Third-Party Integrations
1060
+
1061
+ | Method | Description |
1062
+ |--------|-------------|
1063
+ | `getSuperwallAttributes()` | Get attribution formatted for Superwall |
1064
+ | `getRevenueCatAttributes()` | Get attribution formatted for RevenueCat |
1065
+
1066
+ ### Status
1067
+
1068
+ | Method | Description |
1069
+ |--------|-------------|
1070
+ | `getStatus()` | Get SDK status (initialized, queue stats, online) |
1071
+ | `flush()` | Send all queued events immediately |
1072
+
1073
+ ### Instance Methods (via `datalyr` singleton)
1074
+
1075
+ These methods are available on the `datalyr` instance export, not on the static `Datalyr` class:
1076
+
1077
+ ```typescript
1078
+ import { datalyr } from '@datalyr/react-native';
1079
+ ```
1080
+
1081
+ | Method | Description |
1082
+ |--------|-------------|
1083
+ | `getJourneySummary()` | Get journey summary (first/last touch, touchpoint count) |
1084
+ | `getJourney()` | Get full customer journey (all touchpoints) |
1085
+ | `getPlayInstallReferrer()` | Get raw Play Install Referrer data (Android) |
1086
+
1087
+ ---
1088
+
538
1089
  ## Troubleshooting
539
1090
 
540
1091
  ### Events Not Appearing
@@ -176,7 +176,17 @@ export declare class DatalyrSDK {
176
176
  */
177
177
  getPlayInstallReferrer(): Record<string, any> | null;
178
178
  /**
179
- * Update tracking authorization status on all platform SDKs
179
+ * Get attribution data formatted for Superwall's setUserAttributes()
180
+ * Returns a flat Record<string, string> with only non-empty values
181
+ */
182
+ getSuperwallAttributes(): Record<string, string>;
183
+ /**
184
+ * Get attribution data formatted for RevenueCat's Purchases.setAttributes()
185
+ * Returns a flat Record<string, string> with $-prefixed reserved keys
186
+ */
187
+ getRevenueCatAttributes(): Record<string, string>;
188
+ /**
189
+ * Update tracking authorization status
180
190
  * Call this AFTER the user responds to the ATT permission dialog
181
191
  */
182
192
  updateTrackingAuthorization(enabled: boolean): Promise<void>;
@@ -279,5 +289,7 @@ export declare class Datalyr {
279
289
  };
280
290
  static getAppleSearchAdsAttribution(): AppleSearchAdsAttribution | null;
281
291
  static updateTrackingAuthorization(enabled: boolean): Promise<void>;
292
+ static getSuperwallAttributes(): Record<string, string>;
293
+ static getRevenueCatAttributes(): Record<string, string>;
282
294
  }
283
295
  export default datalyr;
@@ -23,7 +23,7 @@ export class DatalyrSDK {
23
23
  workspaceId: '',
24
24
  apiKey: '',
25
25
  debug: false,
26
- endpoint: 'https://api.datalyr.com', // Updated to server-side API
26
+ endpoint: 'https://ingest.datalyr.com/track',
27
27
  useServerTracking: true, // Default to server-side
28
28
  maxRetries: 3,
29
29
  retryDelay: 1000,
@@ -63,7 +63,7 @@ export class DatalyrSDK {
63
63
  // Set up configuration
64
64
  this.state.config = { ...this.state.config, ...config };
65
65
  // Initialize HTTP client with server-side API
66
- this.httpClient = new HttpClient(this.state.config.endpoint || 'https://api.datalyr.com', {
66
+ this.httpClient = new HttpClient(this.state.config.endpoint || 'https://ingest.datalyr.com/track', {
67
67
  maxRetries: this.state.config.maxRetries || 3,
68
68
  retryDelay: this.state.config.retryDelay || 1000,
69
69
  timeout: this.state.config.timeout || 15000,
@@ -177,7 +177,7 @@ export class DatalyrSDK {
177
177
  const installData = await attributionManager.trackInstall();
178
178
  await this.track('app_install', {
179
179
  platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
180
- sdk_version: '1.6.0',
180
+ sdk_version: '1.6.1',
181
181
  ...installData,
182
182
  });
183
183
  }
@@ -347,10 +347,9 @@ export class DatalyrSDK {
347
347
  }
348
348
  try {
349
349
  debugLog('Fetching deferred web attribution via IP matching...');
350
- const baseUrl = this.state.config.endpoint || 'https://api.datalyr.com';
351
350
  const controller = new AbortController();
352
351
  const timeout = setTimeout(() => controller.abort(), 10000);
353
- const response = await fetch(`${baseUrl}/attribution/deferred-lookup`, {
352
+ const response = await fetch('https://api.datalyr.com/attribution/deferred-lookup', {
354
353
  method: 'POST',
355
354
  headers: {
356
355
  'Content-Type': 'application/json',
@@ -702,8 +701,83 @@ export class DatalyrSDK {
702
701
  const data = playInstallReferrerIntegration.getReferrerData();
703
702
  return data ? playInstallReferrerIntegration.getAttributionData() : null;
704
703
  }
704
+ // MARK: - Third-Party Integration Methods
705
705
  /**
706
- * Update tracking authorization status on all platform SDKs
706
+ * Get attribution data formatted for Superwall's setUserAttributes()
707
+ * Returns a flat Record<string, string> with only non-empty values
708
+ */
709
+ getSuperwallAttributes() {
710
+ const attribution = attributionManager.getAttributionData();
711
+ const advertiser = this.cachedAdvertiserInfo;
712
+ const attrs = {};
713
+ const set = (key, value) => {
714
+ if (value != null && String(value) !== '')
715
+ attrs[key] = String(value);
716
+ };
717
+ set('datalyr_id', this.state.visitorId);
718
+ set('media_source', attribution.utm_source);
719
+ set('campaign', attribution.utm_campaign);
720
+ set('adgroup', attribution.adset_id || attribution.utm_content);
721
+ set('ad', attribution.ad_id);
722
+ set('keyword', attribution.keyword);
723
+ set('network', attribution.network);
724
+ set('utm_source', attribution.utm_source);
725
+ set('utm_medium', attribution.utm_medium);
726
+ set('utm_campaign', attribution.utm_campaign);
727
+ set('utm_term', attribution.utm_term);
728
+ set('utm_content', attribution.utm_content);
729
+ set('lyr', attribution.lyr);
730
+ set('fbclid', attribution.fbclid);
731
+ set('gclid', attribution.gclid);
732
+ set('ttclid', attribution.ttclid);
733
+ set('idfa', advertiser === null || advertiser === void 0 ? void 0 : advertiser.idfa);
734
+ set('gaid', advertiser === null || advertiser === void 0 ? void 0 : advertiser.gaid);
735
+ set('att_status', advertiser === null || advertiser === void 0 ? void 0 : advertiser.att_status);
736
+ return attrs;
737
+ }
738
+ /**
739
+ * Get attribution data formatted for RevenueCat's Purchases.setAttributes()
740
+ * Returns a flat Record<string, string> with $-prefixed reserved keys
741
+ */
742
+ getRevenueCatAttributes() {
743
+ const attribution = attributionManager.getAttributionData();
744
+ const advertiser = this.cachedAdvertiserInfo;
745
+ const attrs = {};
746
+ const set = (key, value) => {
747
+ if (value != null && String(value) !== '')
748
+ attrs[key] = String(value);
749
+ };
750
+ // Reserved attributes ($ prefix)
751
+ set('$datalyrId', this.state.visitorId);
752
+ set('$mediaSource', attribution.utm_source);
753
+ set('$campaign', attribution.utm_campaign);
754
+ set('$adGroup', attribution.adset_id);
755
+ set('$ad', attribution.ad_id);
756
+ set('$keyword', attribution.keyword);
757
+ set('$idfa', advertiser === null || advertiser === void 0 ? void 0 : advertiser.idfa);
758
+ set('$gpsAdId', advertiser === null || advertiser === void 0 ? void 0 : advertiser.gaid);
759
+ if ((advertiser === null || advertiser === void 0 ? void 0 : advertiser.att_status) != null) {
760
+ const statusMap = { 0: 'notDetermined', 1: 'restricted', 2: 'denied', 3: 'authorized' };
761
+ set('$attConsentStatus', statusMap[advertiser.att_status] || String(advertiser.att_status));
762
+ }
763
+ // Custom attributes
764
+ set('utm_source', attribution.utm_source);
765
+ set('utm_medium', attribution.utm_medium);
766
+ set('utm_campaign', attribution.utm_campaign);
767
+ set('utm_term', attribution.utm_term);
768
+ set('utm_content', attribution.utm_content);
769
+ set('lyr', attribution.lyr);
770
+ set('fbclid', attribution.fbclid);
771
+ set('gclid', attribution.gclid);
772
+ set('ttclid', attribution.ttclid);
773
+ set('wbraid', attribution.wbraid);
774
+ set('gbraid', attribution.gbraid);
775
+ set('network', attribution.network);
776
+ set('creative_id', attribution.creative_id);
777
+ return attrs;
778
+ }
779
+ /**
780
+ * Update tracking authorization status
707
781
  * Call this AFTER the user responds to the ATT permission dialog
708
782
  */
709
783
  async updateTrackingAuthorization(enabled) {
@@ -821,7 +895,7 @@ export class DatalyrSDK {
821
895
  carrier: deviceInfo.carrier,
822
896
  network_type: getNetworkType(),
823
897
  timestamp: Date.now(),
824
- sdk_version: '1.6.0',
898
+ sdk_version: '1.6.1',
825
899
  // Advertiser data (IDFA/GAID, ATT status) for server-side postback
826
900
  ...(advertiserInfo ? {
827
901
  idfa: advertiserInfo.idfa,
@@ -1117,6 +1191,13 @@ export class Datalyr {
1117
1191
  static async updateTrackingAuthorization(enabled) {
1118
1192
  await datalyr.updateTrackingAuthorization(enabled);
1119
1193
  }
1194
+ // Third-party integration methods
1195
+ static getSuperwallAttributes() {
1196
+ return datalyr.getSuperwallAttributes();
1197
+ }
1198
+ static getRevenueCatAttributes() {
1199
+ return datalyr.getRevenueCatAttributes();
1200
+ }
1120
1201
  }
1121
1202
  // Export default instance for backward compatibility
1122
1203
  export default datalyr;
@@ -5,7 +5,7 @@ export class HttpClient {
5
5
  this.requestCount = 0;
6
6
  // Use server-side API if flag is set (default to true for v1.0.0)
7
7
  this.endpoint = config.useServerTracking !== false
8
- ? 'https://api.datalyr.com'
8
+ ? 'https://ingest.datalyr.com/track'
9
9
  : endpoint;
10
10
  this.config = config;
11
11
  }
package/lib/types.d.ts CHANGED
@@ -42,11 +42,11 @@ export interface DatalyrConfig {
42
42
  /** Enable console logging for debugging. Default: false */
43
43
  debug?: boolean;
44
44
  /**
45
- * API endpoint URL. Default: 'https://api.datalyr.com'
45
+ * API endpoint URL. Default: 'https://ingest.datalyr.com/track'
46
46
  * @deprecated Use `endpoint` instead
47
47
  */
48
48
  apiUrl?: string;
49
- /** API endpoint URL. Default: 'https://api.datalyr.com' */
49
+ /** API endpoint URL. Default: 'https://ingest.datalyr.com/track' */
50
50
  endpoint?: string;
51
51
  /** Use server-side tracking. Default: true */
52
52
  useServerTracking?: boolean;
@@ -187,3 +187,7 @@ export interface AttributionData {
187
187
  clickId?: string;
188
188
  installTime?: string;
189
189
  }
190
+ /** Flat dictionary for Superwall's setUserAttributes() */
191
+ export type SuperwallAttributes = Record<string, string>;
192
+ /** Flat dictionary for RevenueCat's Purchases.setAttributes() */
193
+ export type RevenueCatAttributes = Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalyr/react-native",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Datalyr SDK for React Native & Expo - Server-side attribution tracking for iOS and Android",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -55,7 +55,7 @@ export class DatalyrSDKExpo {
55
55
  workspaceId: '',
56
56
  apiKey: '',
57
57
  debug: false,
58
- endpoint: 'https://api.datalyr.com',
58
+ endpoint: 'https://ingest.datalyr.com/track',
59
59
  useServerTracking: true,
60
60
  maxRetries: 3,
61
61
  retryDelay: 1000,
@@ -92,7 +92,7 @@ export class DatalyrSDKExpo {
92
92
 
93
93
  this.state.config = { ...this.state.config, ...config };
94
94
 
95
- this.httpClient = new HttpClient(this.state.config.endpoint || 'https://api.datalyr.com', {
95
+ this.httpClient = new HttpClient(this.state.config.endpoint || 'https://ingest.datalyr.com/track', {
96
96
  maxRetries: this.state.config.maxRetries || 3,
97
97
  retryDelay: this.state.config.retryDelay || 1000,
98
98
  timeout: this.state.config.timeout || 15000,
@@ -196,7 +196,7 @@ export class DatalyrSDKExpo {
196
196
  const installData = await attributionManager.trackInstall();
197
197
  await this.track('app_install', {
198
198
  platform: Platform.OS,
199
- sdk_version: '1.6.0',
199
+ sdk_version: '1.6.1',
200
200
  sdk_variant: 'expo',
201
201
  ...installData,
202
202
  });
@@ -606,6 +606,87 @@ export class DatalyrSDKExpo {
606
606
  return appleSearchAdsIntegration.getAttributionData();
607
607
  }
608
608
 
609
+ // MARK: - Third-Party Integration Methods
610
+
611
+ /**
612
+ * Get attribution data formatted for Superwall's setUserAttributes()
613
+ */
614
+ getSuperwallAttributes(): Record<string, string> {
615
+ const attribution = attributionManager.getAttributionData();
616
+ const advertiser = this.cachedAdvertiserInfo;
617
+ const attrs: Record<string, string> = {};
618
+
619
+ const set = (key: string, value: any) => {
620
+ if (value != null && String(value) !== '') attrs[key] = String(value);
621
+ };
622
+
623
+ set('datalyr_id', this.state.visitorId);
624
+ set('media_source', attribution.utm_source);
625
+ set('campaign', attribution.utm_campaign);
626
+ set('adgroup', attribution.adset_id || attribution.utm_content);
627
+ set('ad', attribution.ad_id);
628
+ set('keyword', attribution.keyword);
629
+ set('network', attribution.network);
630
+ set('utm_source', attribution.utm_source);
631
+ set('utm_medium', attribution.utm_medium);
632
+ set('utm_campaign', attribution.utm_campaign);
633
+ set('utm_term', attribution.utm_term);
634
+ set('utm_content', attribution.utm_content);
635
+ set('lyr', attribution.lyr);
636
+ set('fbclid', attribution.fbclid);
637
+ set('gclid', attribution.gclid);
638
+ set('ttclid', attribution.ttclid);
639
+ set('idfa', advertiser?.idfa);
640
+ set('gaid', advertiser?.gaid);
641
+ set('att_status', advertiser?.att_status);
642
+
643
+ return attrs;
644
+ }
645
+
646
+ /**
647
+ * Get attribution data formatted for RevenueCat's Purchases.setAttributes()
648
+ */
649
+ getRevenueCatAttributes(): Record<string, string> {
650
+ const attribution = attributionManager.getAttributionData();
651
+ const advertiser = this.cachedAdvertiserInfo;
652
+ const attrs: Record<string, string> = {};
653
+
654
+ const set = (key: string, value: any) => {
655
+ if (value != null && String(value) !== '') attrs[key] = String(value);
656
+ };
657
+
658
+ // Reserved attributes ($ prefix)
659
+ set('$datalyrId', this.state.visitorId);
660
+ set('$mediaSource', attribution.utm_source);
661
+ set('$campaign', attribution.utm_campaign);
662
+ set('$adGroup', attribution.adset_id);
663
+ set('$ad', attribution.ad_id);
664
+ set('$keyword', attribution.keyword);
665
+ set('$idfa', advertiser?.idfa);
666
+ set('$gpsAdId', advertiser?.gaid);
667
+ if (advertiser?.att_status != null) {
668
+ const statusMap: Record<number, string> = { 0: 'notDetermined', 1: 'restricted', 2: 'denied', 3: 'authorized' };
669
+ set('$attConsentStatus', statusMap[advertiser.att_status] || String(advertiser.att_status));
670
+ }
671
+
672
+ // Custom attributes
673
+ set('utm_source', attribution.utm_source);
674
+ set('utm_medium', attribution.utm_medium);
675
+ set('utm_campaign', attribution.utm_campaign);
676
+ set('utm_term', attribution.utm_term);
677
+ set('utm_content', attribution.utm_content);
678
+ set('lyr', attribution.lyr);
679
+ set('fbclid', attribution.fbclid);
680
+ set('gclid', attribution.gclid);
681
+ set('ttclid', attribution.ttclid);
682
+ set('wbraid', attribution.wbraid);
683
+ set('gbraid', attribution.gbraid);
684
+ set('network', attribution.network);
685
+ set('creative_id', attribution.creative_id);
686
+
687
+ return attrs;
688
+ }
689
+
609
690
  async updateTrackingAuthorization(authorized: boolean): Promise<void> {
610
691
  // Refresh cached advertiser info after ATT status change
611
692
  try {
@@ -700,7 +781,7 @@ export class DatalyrSDKExpo {
700
781
  carrier: deviceInfo.carrier,
701
782
  network_type: networkType,
702
783
  timestamp: Date.now(),
703
- sdk_version: '1.6.0',
784
+ sdk_version: '1.6.1',
704
785
  sdk_variant: 'expo',
705
786
  // Advertiser data (IDFA/GAID, ATT status) for server-side postback
706
787
  ...(advertiserInfo ? {
@@ -960,6 +1041,16 @@ export class DatalyrExpo {
960
1041
  static async updateTrackingAuthorization(authorized: boolean): Promise<void> {
961
1042
  await datalyrExpo.updateTrackingAuthorization(authorized);
962
1043
  }
1044
+
1045
+ // Third-party integration methods
1046
+
1047
+ static getSuperwallAttributes(): Record<string, string> {
1048
+ return datalyrExpo.getSuperwallAttributes();
1049
+ }
1050
+
1051
+ static getRevenueCatAttributes(): Record<string, string> {
1052
+ return datalyrExpo.getRevenueCatAttributes();
1053
+ }
963
1054
  }
964
1055
 
965
1056
  export default datalyrExpo;
@@ -54,7 +54,7 @@ export class DatalyrSDK {
54
54
  workspaceId: '',
55
55
  apiKey: '',
56
56
  debug: false,
57
- endpoint: 'https://api.datalyr.com', // Updated to server-side API
57
+ endpoint: 'https://ingest.datalyr.com/track',
58
58
  useServerTracking: true, // Default to server-side
59
59
  maxRetries: 3,
60
60
  retryDelay: 1000,
@@ -99,7 +99,7 @@ export class DatalyrSDK {
99
99
  this.state.config = { ...this.state.config, ...config };
100
100
 
101
101
  // Initialize HTTP client with server-side API
102
- this.httpClient = new HttpClient(this.state.config.endpoint || 'https://api.datalyr.com', {
102
+ this.httpClient = new HttpClient(this.state.config.endpoint || 'https://ingest.datalyr.com/track', {
103
103
  maxRetries: this.state.config.maxRetries || 3,
104
104
  retryDelay: this.state.config.retryDelay || 1000,
105
105
  timeout: this.state.config.timeout || 15000,
@@ -228,7 +228,7 @@ export class DatalyrSDK {
228
228
  const installData = await attributionManager.trackInstall();
229
229
  await this.track('app_install', {
230
230
  platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
231
- sdk_version: '1.6.0',
231
+ sdk_version: '1.6.1',
232
232
  ...installData,
233
233
  });
234
234
  }
@@ -425,11 +425,10 @@ export class DatalyrSDK {
425
425
  try {
426
426
  debugLog('Fetching deferred web attribution via IP matching...');
427
427
 
428
- const baseUrl = this.state.config.endpoint || 'https://api.datalyr.com';
429
428
  const controller = new AbortController();
430
429
  const timeout = setTimeout(() => controller.abort(), 10000);
431
430
 
432
- const response = await fetch(`${baseUrl}/attribution/deferred-lookup`, {
431
+ const response = await fetch('https://api.datalyr.com/attribution/deferred-lookup', {
433
432
  method: 'POST',
434
433
  headers: {
435
434
  'Content-Type': 'application/json',
@@ -862,8 +861,91 @@ export class DatalyrSDK {
862
861
  return data ? playInstallReferrerIntegration.getAttributionData() : null;
863
862
  }
864
863
 
864
+ // MARK: - Third-Party Integration Methods
865
+
865
866
  /**
866
- * Update tracking authorization status on all platform SDKs
867
+ * Get attribution data formatted for Superwall's setUserAttributes()
868
+ * Returns a flat Record<string, string> with only non-empty values
869
+ */
870
+ getSuperwallAttributes(): Record<string, string> {
871
+ const attribution = attributionManager.getAttributionData();
872
+ const advertiser = this.cachedAdvertiserInfo;
873
+ const attrs: Record<string, string> = {};
874
+
875
+ const set = (key: string, value: any) => {
876
+ if (value != null && String(value) !== '') attrs[key] = String(value);
877
+ };
878
+
879
+ set('datalyr_id', this.state.visitorId);
880
+ set('media_source', attribution.utm_source);
881
+ set('campaign', attribution.utm_campaign);
882
+ set('adgroup', attribution.adset_id || attribution.utm_content);
883
+ set('ad', attribution.ad_id);
884
+ set('keyword', attribution.keyword);
885
+ set('network', attribution.network);
886
+ set('utm_source', attribution.utm_source);
887
+ set('utm_medium', attribution.utm_medium);
888
+ set('utm_campaign', attribution.utm_campaign);
889
+ set('utm_term', attribution.utm_term);
890
+ set('utm_content', attribution.utm_content);
891
+ set('lyr', attribution.lyr);
892
+ set('fbclid', attribution.fbclid);
893
+ set('gclid', attribution.gclid);
894
+ set('ttclid', attribution.ttclid);
895
+ set('idfa', advertiser?.idfa);
896
+ set('gaid', advertiser?.gaid);
897
+ set('att_status', advertiser?.att_status);
898
+
899
+ return attrs;
900
+ }
901
+
902
+ /**
903
+ * Get attribution data formatted for RevenueCat's Purchases.setAttributes()
904
+ * Returns a flat Record<string, string> with $-prefixed reserved keys
905
+ */
906
+ getRevenueCatAttributes(): Record<string, string> {
907
+ const attribution = attributionManager.getAttributionData();
908
+ const advertiser = this.cachedAdvertiserInfo;
909
+ const attrs: Record<string, string> = {};
910
+
911
+ const set = (key: string, value: any) => {
912
+ if (value != null && String(value) !== '') attrs[key] = String(value);
913
+ };
914
+
915
+ // Reserved attributes ($ prefix)
916
+ set('$datalyrId', this.state.visitorId);
917
+ set('$mediaSource', attribution.utm_source);
918
+ set('$campaign', attribution.utm_campaign);
919
+ set('$adGroup', attribution.adset_id);
920
+ set('$ad', attribution.ad_id);
921
+ set('$keyword', attribution.keyword);
922
+ set('$idfa', advertiser?.idfa);
923
+ set('$gpsAdId', advertiser?.gaid);
924
+ if (advertiser?.att_status != null) {
925
+ const statusMap: Record<number, string> = { 0: 'notDetermined', 1: 'restricted', 2: 'denied', 3: 'authorized' };
926
+ set('$attConsentStatus', statusMap[advertiser.att_status] || String(advertiser.att_status));
927
+ }
928
+
929
+ // Custom attributes
930
+ set('utm_source', attribution.utm_source);
931
+ set('utm_medium', attribution.utm_medium);
932
+ set('utm_campaign', attribution.utm_campaign);
933
+ set('utm_term', attribution.utm_term);
934
+ set('utm_content', attribution.utm_content);
935
+ set('lyr', attribution.lyr);
936
+ set('fbclid', attribution.fbclid);
937
+ set('gclid', attribution.gclid);
938
+ set('ttclid', attribution.ttclid);
939
+ set('wbraid', attribution.wbraid);
940
+ set('gbraid', attribution.gbraid);
941
+ set('network', attribution.network);
942
+ set('creative_id', attribution.creative_id);
943
+
944
+ return attrs;
945
+ }
946
+
947
+ /**
948
+ * Update tracking authorization status
867
949
  * Call this AFTER the user responds to the ATT permission dialog
868
950
  */
869
951
  async updateTrackingAuthorization(enabled: boolean): Promise<void> {
@@ -991,7 +1073,7 @@ export class DatalyrSDK {
991
1073
  carrier: deviceInfo.carrier,
992
1074
  network_type: getNetworkType(),
993
1075
  timestamp: Date.now(),
994
- sdk_version: '1.6.0',
1076
+ sdk_version: '1.6.1',
995
1077
  // Advertiser data (IDFA/GAID, ATT status) for server-side postback
996
1078
  ...(advertiserInfo ? {
997
1079
  idfa: advertiserInfo.idfa,
@@ -1364,6 +1446,16 @@ export class Datalyr {
1364
1446
  static async updateTrackingAuthorization(enabled: boolean): Promise<void> {
1365
1447
  await datalyr.updateTrackingAuthorization(enabled);
1366
1448
  }
1449
+
1450
+ // Third-party integration methods
1451
+
1452
+ static getSuperwallAttributes(): Record<string, string> {
1453
+ return datalyr.getSuperwallAttributes();
1454
+ }
1455
+
1456
+ static getRevenueCatAttributes(): Record<string, string> {
1457
+ return datalyr.getRevenueCatAttributes();
1458
+ }
1367
1459
  }
1368
1460
 
1369
1461
  // Export default instance for backward compatibility
@@ -26,8 +26,8 @@ export class HttpClient {
26
26
 
27
27
  constructor(endpoint: string, config: HttpClientConfig) {
28
28
  // Use server-side API if flag is set (default to true for v1.0.0)
29
- this.endpoint = config.useServerTracking !== false
30
- ? 'https://api.datalyr.com'
29
+ this.endpoint = config.useServerTracking !== false
30
+ ? 'https://ingest.datalyr.com/track'
31
31
  : endpoint;
32
32
  this.config = config;
33
33
  }
package/src/types.ts CHANGED
@@ -49,12 +49,12 @@ export interface DatalyrConfig {
49
49
  debug?: boolean;
50
50
 
51
51
  /**
52
- * API endpoint URL. Default: 'https://api.datalyr.com'
52
+ * API endpoint URL. Default: 'https://ingest.datalyr.com/track'
53
53
  * @deprecated Use `endpoint` instead
54
54
  */
55
55
  apiUrl?: string;
56
56
 
57
- /** API endpoint URL. Default: 'https://api.datalyr.com' */
57
+ /** API endpoint URL. Default: 'https://ingest.datalyr.com/track' */
58
58
  endpoint?: string;
59
59
 
60
60
  /** Use server-side tracking. Default: true */
@@ -227,4 +227,12 @@ export interface AttributionData {
227
227
  content?: string;
228
228
  clickId?: string;
229
229
  installTime?: string;
230
- }
230
+ }
231
+
232
+ // Third-Party Integration Attributes
233
+
234
+ /** Flat dictionary for Superwall's setUserAttributes() */
235
+ export type SuperwallAttributes = Record<string, string>;
236
+
237
+ /** Flat dictionary for RevenueCat's Purchases.setAttributes() */
238
+ export type RevenueCatAttributes = Record<string, string>;