diff --git a/api-reference/event-capture.mdx b/api-reference/event-capture.mdx index 66defc4..863c541 100644 --- a/api-reference/event-capture.mdx +++ b/api-reference/event-capture.mdx @@ -40,7 +40,8 @@ POST: /capture-event "currency": "USD", "is_featured": true }, - "user_id": "user_12345" + "user_id": "user_12345", + "event_id": "550e8400-e29b-41d4-a716-446655440000" } ``` @@ -49,6 +50,7 @@ POST: /capture-event | event_name | string | **Required**. Name of the event to track | | event_data | object | **Optional**. Additional data associated with the event | | user_id | string | **Required**. User identifier to associate with the event | +| event_id | string | **Optional**. Unique event identifier (String) for deduplication. If you're tracking the same event from both your app (SDK) and backend (API), make sure to send the same `event_id` from both sources | ### Responses @@ -97,7 +99,8 @@ To enable revenue sharing with ad networks like Google Ads and Meta, include an "category": "electronics", "amount": 149.99 }, - "user_id": "user_12345" + "user_id": "user_12345", + "event_id": "550e8400-e29b-41d4-a716-446655440000" } ``` @@ -106,6 +109,10 @@ To enable revenue sharing with ad networks like Google Ads and Meta, include an string. + + The `event_id` parameter is optional and should be a unique String identifier for each event. It's used for deduplication on the server side. If you're tracking the same event from both your app (SDK) and backend (API), make sure to send the same `event_id` from both sources to prevent duplicate event tracking. + + ## Best Practices 1. **Consistent naming**: Use consistent naming conventions for your events (snake_case is recommended) @@ -118,7 +125,6 @@ To enable revenue sharing with ad networks like Google Ads and Meta, include an ### Tracking a Purchase Event ```javascript -// Using fetch API fetch("https://api.linkrunner.io/api/v1/capture-event", { method: "POST", headers: { @@ -135,6 +141,7 @@ fetch("https://api.linkrunner.io/api/v1/capture-event", { payment_method: "credit_card", }, user_id: "user_12345", + event_id: "550e8400-e29b-41d4-a716-446655440000", // Optional: Unique event identifier for deduplication. Use the same event_id if tracking from both SDK and API }), }) .then((response) => response.json()) diff --git a/sdk/android.mdx b/sdk/android.mdx index cbc7ce7..a63950e 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -335,7 +335,8 @@ private fun trackEvent() { "product_id" to "12345", "category" to "electronics", "amount" to 99.99 // Include amount as a number for revenue sharing with ad networks like Google and Meta - ) + ), + eventId = "550e8400-e29b-41d4-a716-446655440000" // Optional: Unique event identifier (String) for deduplication ) result.onSuccess { @@ -350,6 +351,10 @@ private fun trackEvent() { } ``` + + The `eventId` parameter is optional and should be a unique String identifier for each event. It's used for deduplication on the server side. If you're tracking the same event from both your app and backend, make sure to send the same `eventId` from the [Event Capture API](/api-reference/event-capture) as well. + + ### Revenue Sharing with Ad Networks To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: @@ -364,7 +369,8 @@ private fun trackPurchaseEvent() { "product_id" to "12345", "category" to "electronics", "amount" to 149.99 // Revenue amount as a number - ) + ), + eventId = "550e8400-e29b-41d4-a716-446655440000" // Optional: Unique event identifier (String) for deduplication ) result.onSuccess { @@ -605,7 +611,8 @@ class MainActivity : AppCompatActivity() { try { LinkRunner.getInstance().trackEvent( eventName = "button_clicked", - eventData = mapOf("screen" to "main") + eventData = mapOf("screen" to "main"), + eventId = "550e8400-e29b-41d4-a716-446655440000" // Optional: Unique event identifier (String) for deduplication ) } catch (e: Exception) { println("Error tracking event: ${e.message}") diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index 76cb767..972d09b 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -347,6 +347,7 @@ Future trackEvent() async { 'category': 'electronics', 'amount': 99.99, // Include amount as a number for revenue sharing with ad networks like Google and Meta }, + eventId: '550e8400-e29b-41d4-a716-446655440000', // Optional: Unique event identifier (String) for deduplication ); print('Event tracked successfully'); } catch (e) { @@ -355,6 +356,10 @@ Future trackEvent() async { } ``` + + The `eventId` parameter is optional and should be a unique String identifier for each event. It's used for deduplication on the server side. If you're tracking the same event from both your app and backend, make sure to send the same `eventId` from the [Event Capture API](/api-reference/event-capture) as well. + + ### Revenue Sharing with Ad Networks To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: @@ -369,6 +374,7 @@ Future trackPurchaseEvent() async { 'category': 'electronics', 'amount': 149.99, // Revenue amount as a number }, + eventId: '550e8400-e29b-41d4-a716-446655440000', // Optional: Unique event identifier (String) for deduplication ); print('Purchase event with revenue tracked successfully'); } catch (e) { @@ -737,7 +743,10 @@ class _HomeScreenState extends State { SizedBox(height: 20), ElevatedButton( onPressed: () async { - await LinkRunner().trackEvent(eventName: 'button_clicked'); + await LinkRunner().trackEvent( + eventName: 'button_clicked', + eventId: '550e8400-e29b-41d4-a716-446655440000', // Optional: Unique event identifier (String) for deduplication + ); }, child: Text('Track Custom Event'), ), diff --git a/sdk/flutter/usage.mdx b/sdk/flutter/usage.mdx new file mode 100644 index 0000000..5f0928b --- /dev/null +++ b/sdk/flutter/usage.mdx @@ -0,0 +1,408 @@ +--- +title: "Flutter Usage" +description: "How to use the Linkrunner SDK in your Flutter app" +icon: "flutter" +--- + +## Initializing the SDK + +You'll need your [project token](https://dashboard.linkrunner.io/dashboard?m=documentation) to get started! + +```dart +import 'package:linkrunner/linkrunner.dart'; + +Future initLinkrunner() async { + try { + // Initialize with your project token + await LinkRunner().init( + 'YOUR_PROJECT_TOKEN', + ); + print('LinkRunner initialized'); + } catch (e) { + print('Error initializing LinkRunner: $e'); + } +} + +// Call this in your app's initialization +@override +void initState() { + WidgetsFlutterBinding.ensureInitialized(); // Make sure this is added! + super.initState(); + initLinkrunner(); +} +``` + +Note: The initialization method doesn't return any value. To get attribution data and deeplink information, use the `getAttributionData` method. + +## User Registration + +Call the `signup` method once after the user has completed your app's onboarding process: + +It is strongly recommended to use the integrated platform's identify function to set a persistent user_id once it becomes available (typically after signup or login). + +- [Mixpanel - ID Management & User Identification](https://docs.mixpanel.com/docs/tracking-methods/id-management/identifying-users-simplified) +- [PostHog - How User Identification Works](https://posthog.com/docs/product-analytics/identify#how-identify-works) +- [Amplitude - Identify Users Documentation](https://amplitude.com/docs/get-started/identify-users) + +If the platform's identifier function is not called, you must provide a user identifier for Mixpanel, PostHog, and Amplitude integration. + +- mixpanelDistinctId for Mixpanel +- amplitudeDeviceId for Amplitude +- posthogDistinctId for PostHog + +```dart +Future onSignup() async { + try { + await LinkRunner().signup( + userData: LRUserData( + id: '123', // Required: User ID + name: 'John Doe', // Optional + phone: '9876543210', // Optional + email: 'user@example.com', // Optional + // These properties are used to track reinstalls + userCreatedAt: '2024-01-01T00:00:00Z', // Optional + isFirstTimeUser: true, // Optional + mixpanelDistinctId: 'mixpanelDistinctId', // Optional - Mixpanel Distinct ID + amplitudeDeviceId: 'amplitudeDeviceId', // Optional - Amplitude User ID + posthogDistinctId: 'posthogDistinctId', // Optional - PostHog Distinct ID + ), + data: {}, // Optional: Any additional data + ); + print('Signup successful'); + } catch (e) { + print('Error during signup: $e'); + } +} +``` + +## Getting Attribution Data + +To get attribution data and deeplink information for the current installation, use the `getAttributionData` function: + +```dart +Future getAttributionInfo() async { + try { + final attributionData = await LinkRunner().getAttributionData(); + print('Attribution data: $attributionData'); + } catch (e) { + print('Error getting attribution data: $e'); + } +} +``` + +The `getAttributionData` function returns an `AttributionData` object with the following structure: + +```dart +class AttributionData { + final String? deeplink; // Optional: The deep link URL that led to app installation + final CampaignData campaignData; // Required: Campaign information +} + +class CampaignData { + final String id; // Required: Campaign ID + final String name; // Required: Campaign name + final String? adNetwork; // Optional: "META" | "GOOGLE" | null + final String? groupName; // Optional: Campaign group name + final String? assetGroupName; // Optional: Asset group name + final String? assetName; // Optional: Asset name + final String type; // Required: Campaign type ("ORGANIC" | "INORGANIC") + final String installedAt; // Required: Installation timestamp + final String storeClickAt; // Required: Store click timestamp +} +``` + +Example response: + +```dart +{ + "deeplink": "https://app.yourdomain.com/product/123", + "campaign_data": { + "id": "camp_123", + "name": "Summer Sale 2024", + "ad_network": "META", + "group_name": "iOS Campaign", + "asset_group_name": "Product Catalog", + "asset_name": "Banner Ad 1", + "type": "INORGANIC", + "installed_at": "2024-03-20T10:30:00Z", + "store_click_at": "2024-03-20T10:29:45Z" + } +} +``` + +## Set Additional Data + +Use the `setAdditionalData` method to set CleverTap ID: + +```dart +Future setIntegrationData() async { + try { + await LinkRunner().setAdditionalData( + integrationData: { + 'clevertap_id': 'YOUR_CLEVERTAP_USER_ID', // CleverTap user identifier + }, + ); + print('CleverTap ID set successfully'); + } catch (e) { + print('Error setting CleverTap ID: $e'); + } +} +``` + +### Parameters for `LinkRunner.setAdditionalData` + +- `clevertap_id`: String (optional) - CleverTap user identifier + +This method allows you to connect user identities across different analytics and marketing platforms. + +## Tracking Custom Events + +Track custom events in your app: + +```dart +Future trackEvent() async { + try { + await LinkRunner().trackEvent( + eventName: 'purchase_initiated', // Event name + eventData: { // Optional: Event data + 'product_id': '12345', + 'category': 'electronics', + 'amount': 99.99, // Include amount as a number for revenue sharing with ad networks like Google and Meta + }, + eventId: '550e8400-e29b-41d4-a716-446655440000', // Optional: Unique event identifier (String) for deduplication + ); + print('Event tracked successfully'); + } catch (e) { + print('Error tracking event: $e'); + } +} +``` + + + The `eventId` parameter is optional and should be a unique String identifier for each event. It's used for deduplication on the server side. If you're tracking the same event from both your app and backend, make sure to send the same `eventId` from the [Event Capture API](/api-reference/event-capture) as well. + + +### Revenue Sharing with Ad Networks + +To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: + +```dart +Future trackPurchaseEvent() async { + try { + await LinkRunner().trackEvent( + eventName: 'purchase_completed', + eventData: { + 'product_id': '12345', + 'category': 'electronics', + 'amount': 149.99, // Revenue amount as a number + }, + eventId: '550e8400-e29b-41d4-a716-446655440000', // Optional: Unique event identifier (String) for deduplication + ); + print('Purchase event with revenue tracked successfully'); + } catch (e) { + print('Error tracking purchase event: $e'); + } +} +``` + + + For revenue sharing with ad networks to work properly, ensure the `amount` parameter is passed as a number (double + or int), not as a string. + + +## Revenue Tracking + +### Capturing Payments + +Track payment information: + +```dart +Future capturePayment() async { + try { + await LinkRunner().capturePayment( + capturePayment: LRCapturePayment( + amount: 99.99, // Required: Payment amount + userId: 'user123', // Required: User identifier + paymentId: 'payment456', // Optional: Unique payment identifier + type: PaymentType.FIRST_PAYMENT, // Optional: Payment type + // type: PaymentType.SECOND_PAYMENT, // Optional: Payment type + status: PaymentStatus.PAYMENT_COMPLETED, // Optional: Payment status + ), + ); + print('Payment captured successfully'); + } catch (e) { + print('Error capturing payment: $e'); + } +} + +Future removePayment() async { + try { + await LinkRunner().removePayment( + removePayment: LRRemovePayment( + userId: 'user123', // Either userId or paymentId must be provided + paymentId: 'payment456', // Optional: Unique payment identifier + ), + ); + print('Payment removed successfully'); + } catch (e) { + print('Error removing payment: $e'); + } +} +``` + +#### Parameters for `LRCapturePayment` + +- `amount`: double (required) - The payment amount +- `userId`: String (required) - Identifier for the user making the payment +- `paymentId`: String (optional) - Unique identifier for the payment +- `type`: PaymentType (optional) - Type of payment. Available options: + - `PaymentType.FIRST_PAYMENT` - First payment made by the user + - `PaymentType.SECOND_PAYMENT` - Second payment made by the user + - `PaymentType.WALLET_TOPUP` - Adding funds to a wallet + - `PaymentType.FUNDS_WITHDRAWAL` - Withdrawing funds + - `PaymentType.SUBSCRIPTION_CREATED` - New subscription created + - `PaymentType.SUBSCRIPTION_RENEWED` - Subscription renewal + - `PaymentType.ONE_TIME` - One-time payment + - `PaymentType.RECURRING` - Recurring payment + - `PaymentType.DEFAULT_PAYMENT` - Default type (used if not specified) +- `status`: PaymentStatus (optional) - Status of the payment. Available options: + - `PaymentStatus.PAYMENT_INITIATED` - Payment has been initiated + - `PaymentStatus.PAYMENT_COMPLETED` - Payment completed successfully (default if not specified) + - `PaymentStatus.PAYMENT_FAILED` - Payment attempt failed + - `PaymentStatus.PAYMENT_CANCELLED` - Payment was cancelled + +## SDK Signing (Optional) + +The SDK signing feature provides the following parameters during initialization: + +- **`secretKey`**: A unique secret key used for request signing and authentication +- **`keyId`**: A unique identifier for the key pair used in the signing process + +You can find your project token, secret key, and key ID [here](https://www.linkrunner.io/settings?s=sdk-signing). + +```dart +import 'dart:io' show Platform; +import 'package:linkrunner/linkrunner.dart'; + +Future initLinkrunnerWithSigning() async { + try { + // Initialize with your project token and SDK signing parameters + await LinkRunner().init( + 'YOUR_PROJECT_TOKEN', + Platform.isIOS ? 'YOUR_IOS_SECRET_KEY' : 'YOUR_ANDROID_SECRET_KEY', // Platform-specific secret key + Platform.isIOS ? 'YOUR_IOS_KEY_ID' : 'YOUR_ANDROID_KEY_ID', // Platform-specific key ID + true, // Optional: Enable debug mode for development (defaults to false) + ); + print('LinkRunner initialized with SDK signing'); + } catch (e) { + print('Error initializing LinkRunner: $e'); + } +} +``` + +## Enhanced Privacy Controls + +The SDK offers options to enhance user privacy: + +```dart +// Enable PII (Personally Identifiable Information) hashing +LinkRunner().enablePIIHashing(true); +``` + +When PII hashing is enabled, sensitive user data like name, email, and phone number are hashed using SHA-256 before being sent to Linkrunner servers. + +## Function Placement Guide + +| Function | Where to Place | When to Call | +| --------------------------------- | ------------------------------ | ---------------------------------------------- | +| `LinkRunner().init` | App initialization | Once when app starts | +| `LinkRunner().getAttributionData` | Attribution data handling flow | Whenever the attribution data is needed | +| `LinkRunner().setAdditionalData` | Integration code | When third-party integration IDs are available | +| `LinkRunner().signup` | Onboarding flow | Once after user completes onboarding | +| `LinkRunner().setUserData` | Authentication logic | Every time app opens with logged-in user | +| `LinkRunner().trackEvent` | Throughout app | When specific user actions occur | +| `LinkRunner().capturePayment` | Payment processing | When user makes a payment | +| `LinkRunner().removePayment` | Refund flow | When payment needs to be removed | + +## Complete Example + +Here's a simplified example showing how to integrate Linkrunner in a Flutter app: + +You can find your project token [here](https://www.linkrunner.io/dashboard?s=members&m=documentation). + +```dart +import 'package:flutter/material.dart'; +import 'package:linkrunner/linkrunner.dart'; + + +final linkrunner = LinkRunner(); + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Linkrunner Demo', + home: HomeScreen(), + ); + } +} + +class HomeScreen extends StatefulWidget { + @override + _HomeScreenState createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + bool _initialized = false; + + @override + void initState() { + super.initState(); + _initializeLinkrunner(); + } + + Future _initializeLinkrunner() async { + try { + await LinkRunner().init('YOUR_PROJECT_TOKEN'); + setState(() { + _initialized = true; + }); + } catch (e) { + print('Error initializing LinkRunner: $e'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Linkrunner Demo')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('LinkRunner ${_initialized ? 'Initialized' : 'Initializing...'}'), + SizedBox(height: 20), + ElevatedButton( + onPressed: () async { + await LinkRunner().trackEvent( + eventName: 'button_clicked', + eventId: '550e8400-e29b-41d4-a716-446655440000', // Optional: Unique event identifier (String) for deduplication + ); + }, + child: Text('Track Custom Event'), + ), + ], + ), + ), + ); + } +} +``` + +## Support + +If you encounter issues during integration, contact us at [darshil@linkrunner.io](mailto:darshil@linkrunner.io). diff --git a/sdk/ios.mdx b/sdk/ios.mdx index fb031ca..3d88ab1 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -271,7 +271,8 @@ func trackEvent() async { "product_id": "12345", "category": "electronics", "amount": 99.99 // Include amount as a number for revenue sharing with ad networks like Google and Meta - ] + ], + eventId: "550e8400-e29b-41d4-a716-446655440000" // Optional: Unique event identifier (String) for deduplication ) print("Event tracked successfully") } catch { @@ -280,6 +281,10 @@ func trackEvent() async { } ``` + + The `eventId` parameter is optional and should be a unique String identifier for each event. It's used for deduplication on the server side. If you're tracking the same event from both your app and backend, make sure to send the same `eventId` from the [Event Capture API](/api-reference/event-capture) as well. + + ### Revenue Sharing with Ad Networks To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: @@ -293,7 +298,8 @@ func trackPurchaseEvent() async { "product_id": "12345", "category": "electronics", "amount": 149.99 // Revenue amount as a number - ] + ], + eventId: "550e8400-e29b-41d4-a716-446655440000" // Optional: Unique event identifier (String) for deduplication ) print("Purchase event with revenue tracked successfully") } catch { @@ -411,6 +417,7 @@ You can find your project token [here](https://dashboard.linkrunner.io/dashboard ```swift import SwiftUI import Linkrunner +import Foundation @main struct MyApp: App { @@ -458,7 +465,8 @@ struct ContentView: View { do { try await LinkrunnerSDK.shared.trackEvent( eventName: "button_clicked", - eventData: ["screen": "home"] + eventData: ["screen": "home"], + eventId: "550e8400-e29b-41d4-a716-446655440000" // Optional: Unique event identifier (String) for deduplication ) print("Event tracked successfully") } catch { diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index ebe32b1..e2e8ff4 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -213,22 +213,31 @@ Track custom events in your app: const trackEvent = async () => { await linkrunner.trackEvent( "purchase_initiated", // Event name - { product_id: "12345", category: "electronics", amount: 99.99 } // Optional: Event data, include amount as a number for revenue sharing with ad networks like Google and Meta + { product_id: "12345", category: "electronics", amount: 99.99 }, // Optional: Event data, include amount as a number for revenue sharing with ad networks like Google and Meta + "550e8400-e29b-41d4-a716-446655440000" // Optional: Unique event identifier (String) for deduplication ); }; ``` + + The `eventId` parameter is optional and should be a unique String identifier for each event. It's used for deduplication on the server side. If you're tracking the same event from both your app and backend, make sure to send the same `eventId` from the [Event Capture API](/api-reference/event-capture) as well. + + ### Revenue Sharing with Ad Networks To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: ```javascript const trackPurchaseEvent = async () => { - await linkrunner.trackEvent("purchase_completed", { - product_id: "12345", - category: "electronics", - amount: 149.99, // Revenue amount as a number - }); + await linkrunner.trackEvent( + "purchase_completed", + { + product_id: "12345", + category: "electronics", + amount: 149.99, // Revenue amount as a number + }, + "550e8400-e29b-41d4-a716-446655440000" // Optional: Unique event identifier (String) for deduplication + ); }; ```