diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..66a1024 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,195 @@ +# Linkrunner Docs — Writing Guide + +This file defines the writing conventions for the Linkrunner documentation site. Follow these rules when creating or editing any page to keep the docs consistent. + +## Branding + +- Always write **Linkrunner** (capital L, lowercase r). Never "LinkRunner", "linkRunner", or "LINKRUNNER". +- Exception: SDK class names in code examples use the actual class name (`LinkRunner` in Kotlin/Dart/Swift, `linkrunner` in npm packages). Do not change these. +- The product is a **Mobile Measurement Partner (MMP)**. +- Dashboard URL: `https://dashboard.linkrunner.io` +- API base URL: `https://api.linkrunner.io/api/v1` +- Support email: `darshil@linkrunner.io` + +## File format + +- All pages are **MDX** files with YAML front matter. +- The site is built with [Mintlify](https://mintlify.com). Site config lives in `docs.json`. +- Every page must have front matter with `title`, `description`, and `icon`: + +```yaml +--- +title: "Page Title" +description: "One-sentence summary for SEO and navigation" +icon: "icon-name" +--- +``` + +- Icons come from the [Font Awesome](https://fontawesome.com/icons) set (Mintlify subset). + +## Tone and voice + +- **Professional but approachable.** Write like you're explaining something to a smart colleague, not writing a legal document. +- **Second person.** Address the reader as "you" / "your". +- **Active voice.** "Linkrunner sends a POST request" not "A POST request is sent by Linkrunner". +- **Imperative for instructions.** "Install the SDK", "Enter your App ID", "Click Save". +- **Short paragraphs.** Keep paragraphs to 1-3 sentences. Docs are scanned, not read cover-to-cover. +- **No filler words.** Cut "simply", "just", "easily", "please note that" unless they genuinely add meaning. +- **No emojis** in documentation content. + +## Structure + +Every page should follow this general flow (skip sections that don't apply): + +1. **Intro paragraph** — 1-2 sentences explaining what this page covers and why it matters. +2. **Core content** — The main explanation, setup steps, or reference material. +3. **Examples** — Real, runnable code examples with realistic data. +4. **Best practices** — Actionable recommendations (if applicable). +5. **Troubleshooting** — Common issues and solutions (if applicable). +6. **Related / Next steps** — Links to connected pages using ``. +7. **Support footer** — `For any help please reach out to [darshil@linkrunner.io](mailto:darshil@linkrunner.io)` + +### Headings + +- Use `##` (H2) for top-level sections. +- Use `###` (H3) for subsections. +- Avoid H4+ — if you need that much nesting, restructure the content. +- Use sentence case for headings: "Ad network parameter mapping" not "Ad Network Parameter Mapping". + +## Mintlify components + +Use these components where appropriate: + +### Notes, warnings, and tips + +```mdx + + Informational callout — useful context that isn't critical. + + + + Critical information the reader must not miss. Use sparingly. + + + + Helpful suggestion that improves the reader's workflow. + +``` + +### Cards + +Use `` with `` for navigation links and feature overviews: + +```mdx + + + Short description + + +``` + +### Tabs + +Use `` when showing platform-specific content (React Native, Flutter, iOS, Android): + +```mdx + + + Content for React Native + + + Content for Flutter + + +``` + +### Steps + +Use `` for sequential processes: + +```mdx + + + Description of what to do. + + +``` + +## Code examples + +- Use fenced code blocks with a language identifier: ` ```json `, ` ```javascript `, ` ```bash `, ` ```dart `, ` ```kotlin `, ` ```xml `, ` ```tsx `. +- Use realistic but fake data in examples (e.g. `user_12345`, `sku_12345`, `order_98765`). +- Keep examples minimal — show exactly what's needed, nothing more. +- Add a short sentence before each code block explaining what it demonstrates. +- When showing API payloads, use 4-space indentation in JSON. +- For inline code, use backticks: `event_data`, `capture-event`, `linkrunner-key`. + +## Tables + +- Use Markdown pipe tables for parameter references and field descriptions. +- Standard columns for API parameters: `Parameter`, `Type`, `Description`. +- Mark required fields with **Required** in bold inside the description. +- Use `string \| null` for nullable types in TypeScript type tables. + +```markdown +| Parameter | Type | Description | +|-----------|--------|--------------------------------------| +| user_id | string | **Required**. User identifier | +| event_id | string | Optional. Unique event ID | +``` + +## Links + +- Use relative paths for internal links: `[Event Capture API](/api-reference/event-capture)`. +- Use full URLs for external links: `[Meta for Developers](https://developers.facebook.com/)`. +- Use `mailto:` for email links: `[darshil@linkrunner.io](mailto:darshil@linkrunner.io)`. +- Dashboard settings links should include query params for direct navigation: `https://dashboard.linkrunner.io/settings?s=meta-integration`. + +## Naming conventions + +- Use **snake_case** for API parameter names, event names, and field names: `event_name`, `capture-event`, `product_id`. +- Use **kebab-case** for URL paths and file names: `custom-event-parameters.mdx`, `/api-reference/event-capture`. +- Ad network names: **Meta**, **Google Ads**, **TikTok**, **Snapchat**, **Apple Search Ads** (proper case). +- SDK names: **React Native**, **Flutter**, **iOS**, **Android**, **Expo**, **Web**. + +## Common patterns + +### API reference pages + +``` +- Base URL section +- Authentication section (with linkrunner-key header) +- Endpoint(s) with method and path +- Request body with JSON example +- Parameter table +- Response codes +- Sample response JSON +- TypeScript types (if applicable) +- Error handling table +- Support footer +``` + +### Feature/setup pages + +``` +- Overview paragraph with use cases +- Prerequisites or requirements (if any) +- Step-by-step setup (use or numbered headings) +- Platform-specific content in +- Configuration options (tables) +- Testing/verification +- Troubleshooting +- Support footer +``` + +### SDK guide pages + +``` +- Installation instructions per platform +- Initialization code +- Core methods (signup, attribution, events, payments) +- Platform-specific tabs +- Method reference table (Method | Where to call | When to call) +- Full working example +- Support footer +``` diff --git a/api-reference/custom-event-parameters.mdx b/api-reference/custom-event-parameters.mdx new file mode 100644 index 0000000..89b90e3 --- /dev/null +++ b/api-reference/custom-event-parameters.mdx @@ -0,0 +1,208 @@ +--- +title: "Custom Event Parameters" +description: "Send product and event details with your events so Linkrunner can forward them to ad networks automatically" +icon: "list" +--- + +When you track events or payments through Linkrunner, you can include an `event_data` object with additional details like product IDs, order IDs, and item counts. Linkrunner automatically translates these parameters into the format each ad network expects, so you only need to send them once. + +## Why send event parameters? + +Ad networks like Meta, Google, TikTok, and Snapchat use event parameters to: + +- **Match conversions to product catalogs** — Meta uses `product_id` to connect purchases to your product feed +- **Deduplicate events** — `order_id` prevents the same purchase from being counted twice +- **Optimize ad delivery** — richer event data helps ad networks find higher-value users +- **Power reporting** — parameters like `content_type` and `content_category` improve breakdown reports + +## How it works + + + + Include an `event_data` object in your [`capture-event`](/api-reference/event-capture) or [`capture-payment`](/api-reference/revenue-tracking) API call. + + + The full `event_data` object is saved alongside the event in your Linkrunner dashboard. + + + Standard parameters (like `product_id`, `order_id`) are automatically translated to each network's field names. Custom parameters are passed through as-is to networks that support them. + + + The `amount` and `currency` fields are used for revenue reporting across all networks. + + + +## Standard parameters + +These parameters are recognized by Linkrunner and automatically mapped to each ad network's expected format. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `product_id` | `string` or `string[]` | Product SKU or identifier. Required by Meta for product catalog matching. | +| `order_id` | `string` | Transaction or order identifier. Used for deduplication across ad networks. | +| `content_type` | `string` | Set to `"product"` for a specific SKU or `"product_group"` for product variants. | +| `num_items` | `number` | Number of items in the transaction. | +| `contents` | `array` | Array of `{ id, quantity, item_price }` objects for multi-product events. | +| `search_string` | `string` | The user's search query text. | +| `content_name` | `string` | Name of the content or product. | +| `content_category` | `string` | Category of the content or product. | +| `description` | `string` | Description of the event or product. | +| `amount` | `number` | Revenue value for the event. | +| `currency` | `string` | ISO 4217 currency code (e.g. `"USD"`, `"EUR"`). | + + + Any key not listed above (e.g. `coupon_code`, `payment_plan`, `subscription_tier`) is treated as a custom parameter and passed through to ad networks that support custom properties. Currently, only Meta accepts custom parameters. + + +## Ad network parameter mapping + +When you send a standard parameter, Linkrunner translates it to the field name each ad network expects. A dash (`—`) means that network does not support the parameter. + +| Linkrunner parameter | Meta CAPI | Google Ads | TikTok | Snapchat | +|----------------------|-----------|------------|--------|----------| +| `product_id` | `content_ids` | `item_id` | `content_ids` | `item_ids` | +| `order_id` | `order_id` | `order_id` | `order_id` (purchase only) | `transaction_id` | +| `content_type` | `content_type` | `category` | Auto-derived from `content_ids` | — | +| `num_items` | `num_items` | — | — | `number_items` | +| `contents` | `contents` | — | — | — | +| `search_string` | `search_string` | `search_string` | `search_string` (custom events only) | — | +| `description` | — | — | `description` | — | +| `content_name` | `content_name` | — | — | — | +| `content_category` | `content_category` | — | — | — | +| `amount` / `value` | `value` | `value` (query param) | `value` | `value` | +| `currency` | `currency` | `currency_code` (query param) | `currency` | `currency` | +| *any custom key* | Custom property | — | — | — | + + + You don't need to worry about these mappings. Just send your parameters using the names in the "Linkrunner parameter" column and Linkrunner handles the rest. + + +## Best practices + +- **Always include `product_id` for purchase events.** Meta requires it for product catalog matching. If you skip it, Meta can't connect the purchase to a product in your catalog. +- **Include `order_id` for deduplication.** Ad networks use this to ensure the same transaction isn't counted twice, especially when events are sent from both your app and server. +- **Use `content_type` to describe what `product_id` refers to.** Set it to `"product"` for a specific SKU, or `"product_group"` when the ID covers multiple variants. +- **Don't duplicate `amount` and `currency` inside `event_data` for payments.** When using [`capture-payment`](/api-reference/revenue-tracking), the top-level `amount` and `currency` fields are used for revenue tracking. Putting them in `event_data` as well can cause double-counting. +- **Custom keys are forwarded to Meta only.** Parameters like `coupon_code` or `subscription_tier` are passed through as custom properties to Meta CAPI. Other networks do not receive them. + +## Examples + +### Purchase event (via capture-event) + +Send product details alongside a purchase event tracked from your backend. + +```json +{ + "event_name": "purchase_completed", + "event_data": { + "product_id": "sku_12345", + "order_id": "order_98765", + "content_type": "product", + "num_items": 2, + "amount": 49.99, + "currency": "USD", + "coupon_code": "SAVE20" + }, + "user_id": "user_12345" +} +``` + +### Payment with product details (via capture-payment) + +When using the payment API, put product details in `event_data` and keep `amount`/`currency` at the top level. + +```json +{ + "user_id": "user_12345", + "payment_id": "pay_abc123", + "amount": 49.99, + "currency": "USD", + "type": "ONE_TIME", + "event_data": { + "product_id": "sku_12345", + "order_id": "order_98765", + "content_type": "product", + "num_items": 2 + } +} +``` + + + Notice that `amount` and `currency` are **not** inside `event_data` here. For `capture-payment`, always use the top-level fields for revenue. + + +### Add to cart + +```json +{ + "event_name": "cart_added", + "event_data": { + "product_id": "SKU-789", + "content_type": "product", + "num_items": 1, + "currency": "USD", + "amount": 29.99 + }, + "user_id": "user_12345" +} +``` + +### Search + +```json +{ + "event_name": "search_performed", + "event_data": { + "search_string": "running shoes", + "content_type": "product" + }, + "user_id": "user_12345" +} +``` + +### Subscription + +```json +{ + "user_id": "user_12345", + "payment_id": "sub_xyz789", + "amount": 9.99, + "currency": "USD", + "type": "SUBSCRIPTION_CREATED", + "event_data": { + "product_id": "plan_premium", + "order_id": "sub_xyz789", + "payment_plan": "monthly" + } +} +``` + +### Custom event with arbitrary parameters + +Any event can include custom key-value pairs. These are stored by Linkrunner and forwarded to Meta as custom properties. + +```json +{ + "event_name": "level_completed", + "event_data": { + "level_number": 5, + "score": 12500, + "time_spent_seconds": 180, + "difficulty": "hard" + }, + "user_id": "user_12345" +} +``` + +## Related + + + + Track custom events from your backend + + + Capture payments and revenue data + + + +For any help please reach out to [darshil@linkrunner.io](mailto:darshil@linkrunner.io) diff --git a/api-reference/event-capture.mdx b/api-reference/event-capture.mdx index 66defc4..f55b34b 100644 --- a/api-reference/event-capture.mdx +++ b/api-reference/event-capture.mdx @@ -32,23 +32,25 @@ POST: /capture-event ```json { - "event_name": "product_viewed", + "event_name": "purchase_completed", "event_data": { "product_id": "ABC123", - "category": "electronics", - "amount": 249.99, - "currency": "USD", - "is_featured": true + "order_id": "ORD-12345", + "content_type": "product", + "num_items": 2, + "amount": 149.99, + "currency": "USD" }, "user_id": "user_12345" } ``` -| Parameter | Type | Description | -| ---------- | ------ | --------------------------------------------------------- | -| 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 | +| Parameter | Type | Description | +| ---------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| event_name | string | **Required**. Name of the event to track | +| event_data | object | **Optional**. Event parameters — standard keys are mapped to ad networks automatically, custom keys are passed through as-is | +| 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 @@ -85,27 +87,32 @@ Here are some common event names you might want to track: | `achievement_unlocked` | User unlocks an achievement | | `user_referred` | User refers someone | -## 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: - -```json -{ - "event_name": "purchase_completed", - "event_data": { - "product_id": "ABC123", - "category": "electronics", - "amount": 149.99 - }, - "user_id": "user_12345" -} -``` +## Event Data Parameters - For revenue sharing with ad networks to work properly, ensure the `amount` parameter is passed as a number, not as a - string. + The `event_data` field accepts a flat key-value map. Standard parameters are recognized and automatically mapped to each ad network's expected format. Any additional keys are passed through as custom parameters. +**Standard parameters:** + +| Parameter | Type | Description | +| ---------------- | ----------------- | -------------------------------------------------------------- | +| product_id | string or string[] | Product SKU/identifier | +| order_id | string | Transaction/order identifier | +| content_type | string | "product" or "product_group" | +| num_items | number | Number of items in the transaction | +| contents | array | Array of `{ id, quantity, item_price }` for multi-product events | +| search_string | string | Search query text | +| content_name | string | Name of the content/product | +| content_category | string | Category of the content/product | +| description | string | Description of the event or product | +| amount | number | Revenue value for the event | +| currency | string | ISO 4217 currency code | + +Any keys not listed above (e.g. `coupon_code`, `payment_plan`, `subscription_tier`) are passed through to ad networks that support custom properties (currently Meta). + +For the full parameter mapping across ad networks, see [Custom Event Parameters](/api-reference/custom-event-parameters). + ## 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: { @@ -128,11 +134,13 @@ fetch("https://api.linkrunner.io/api/v1/capture-event", { body: JSON.stringify({ event_name: "purchase_completed", event_data: { + product_id: "P-001", order_id: "ORD-12345", - product_ids: ["P-001", "P-002"], - total_amount: 125.99, + content_type: "product", + num_items: 1, + amount: 125.99, currency: "USD", - payment_method: "credit_card", + coupon_code: "SAVE20", }, user_id: "user_12345", }), @@ -142,6 +150,52 @@ fetch("https://api.linkrunner.io/api/v1/capture-event", { .catch((error) => console.error("Error:", error)); ``` +### Add to Cart + +```json +{ + "event_name": "cart_added", + "event_data": { + "product_id": "SKU-789", + "content_type": "product", + "num_items": 1, + "currency": "USD", + "amount": 29.99 + }, + "user_id": "user_12345" +} +``` + +### Search + +```json +{ + "event_name": "search_performed", + "event_data": { + "search_string": "running shoes", + "content_type": "product" + }, + "user_id": "user_12345" +} +``` + +### Subscription + +```json +{ + "event_name": "subscription_started", + "event_data": { + "product_id": "plan_premium_monthly", + "order_id": "sub_abc123", + "amount": 9.99, + "currency": "USD", + "payment_plan": "monthly", + "trial_period_days": 7 + }, + "user_id": "user_12345" +} +``` + ## Error Handling The API will return appropriate HTTP status codes along with error messages when issues occur: diff --git a/api-reference/revenue-tracking.mdx b/api-reference/revenue-tracking.mdx index 436fed9..08b4f7d 100644 --- a/api-reference/revenue-tracking.mdx +++ b/api-reference/revenue-tracking.mdx @@ -31,11 +31,74 @@ POST: /capture-payment ```json { "user_id": "666", - "payment_id": "ABC", // optional but recommended - "amount": 25096, // Send amount in one currency only - "type": "FIRST_PAYMENT", // optional - // "type": "SECOND_PAYMENT", // optional - "status": "PAYMENT_COMPLETED" // optional + "payment_id": "ABC", + "amount": 25096, + "currency": "USD", + "type": "FIRST_PAYMENT", + "status": "PAYMENT_COMPLETED", + "event_data": { + "product_id": "sku_12345", + "order_id": "order_98765", + "content_type": "product", + "num_items": 1 + } +} +``` + +| Parameter | Type | Description | +| ---------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- | +| user_id | string | **Required**. User identifier | +| payment_id | string | Optional but recommended. Unique payment identifier for deduplication | +| amount | number | Optional. Payment amount. Send in one currency only | +| currency | string | Optional. ISO 4217 currency code (e.g. "USD", "INR") | +| type | string | Optional. Payment type (defaults to DEFAULT) | +| status | string | Optional. Payment status (defaults to PAYMENT_COMPLETED) | +| event_data | object | Optional. Product and event parameters forwarded to ad networks (see [Standard Parameters](/api-reference/custom-event-parameters)) | + +### Event Data for Payments + + + The `event_data` field lets you send product-level details alongside your payment. Standard parameters are automatically mapped to each ad network's format. Custom keys are passed through as-is. + + + + `amount` and `currency` from the top-level request body are used for revenue tracking. Do not duplicate them inside `event_data`. + + +**Purchase with product details:** + +```json +{ + "user_id": "user_12345", + "payment_id": "pay_abc123", + "amount": 49.99, + "currency": "USD", + "type": "ONE_TIME", + "event_data": { + "product_id": "sku_12345", + "order_id": "order_98765", + "content_type": "product", + "num_items": 2, + "coupon_code": "SAVE20" + } +} +``` + +**Subscription with custom params:** + +```json +{ + "user_id": "user_12345", + "payment_id": "sub_xyz789", + "amount": 9.99, + "currency": "USD", + "type": "SUBSCRIPTION_CREATED", + "event_data": { + "product_id": "plan_premium", + "order_id": "sub_xyz789", + "payment_plan": "monthly", + "trial_converted": true + } } ``` diff --git a/docs.json b/docs.json index df95629..60382b6 100644 --- a/docs.json +++ b/docs.json @@ -85,6 +85,7 @@ "api-reference/data-apis", "api-reference/revenue-tracking", "api-reference/event-capture", + "api-reference/custom-event-parameters", "api-reference/campaign-apis" ] } diff --git a/sdk/android.mdx b/sdk/android.mdx index cbc7ce7..bb80113 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -179,7 +179,7 @@ class MyApplication : Application() { ### SDK Signing Parameters (Optional) -For enhanced security, the LinkRunner SDK requires the following signing parameters during initialization: +For enhanced security, the Linkrunner SDK requires the following signing 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 @@ -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..32eb9b1 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -126,7 +126,7 @@ You'll need your [project token](https://dashboard.linkrunner.io/dashboard?m=doc ### SDK Signing Parameters (Optional) -For enhanced security, the LinkRunner SDK requires the following signing parameters during initialization: +For enhanced security, the Linkrunner SDK requires the following signing 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 @@ -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..7cfa927 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -122,7 +122,7 @@ struct MyApp: App { ### SDK Signing Parameters (Optional) -For enhanced security, the LinkRunner SDK requires the following signing parameters during initialization: +For enhanced security, the Linkrunner SDK requires the following signing 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 @@ -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..b4cd9ab 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -64,7 +64,7 @@ You can find your project token [here](https://dashboard.linkrunner.io/dashboard ### SDK Signing Parameters (Optional) -For enhanced security, the LinkRunner SDK requires the following signing parameters during initialization: +For enhanced security, the Linkrunner SDK requires the following signing 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 @@ -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 + ); }; ```