Skip to main content

Overview

SDK for UTM attribution in Flutter apps. Capture UTMs received via deep links and associate user sessions with the source tracking in Android and iOS.

Requirements

  • Flutter 3.29+
  • Dart 3.10+
  • Android: minSdkVersion 21
  • iOS: Deployment Target 11.0+

Installation

1. Add the dependency

To get the nemu_attribution library, contact the support team and receive a .zip file. After you receive the .zip, extract the nemu_attribution folder into ./flutter/nemu_attribution (in your Flutter project). In your project’s pubspec.yaml, add the package:
dependencies:
  nemu_attribution:
    path: ./flutter/nemu_attribution
Then install the dependencies:
flutter pub get

2. Android configuration

In android/app/src/main/AndroidManifest.xml, add the intent-filter inside the main <activity> to enable deep links:
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="https" android:host="app.seudominio.com" />
</intent-filter>

3. iOS configuration

In Xcode, go to Signing & Capabilities and add Associated Domains with the value:
applinks:app.seudominio.com
Make sure the apple-app-site-association file is available on the configured domain.

Credentials

The SDK needs two credentials provided by Nemu: PIXEL_ID and SDK_TOKEN.
We recommend using environment variables so you don’t expose credentials in your source code.
You can use the flutter_dotenv package or inject them via --dart-define:
flutter run \
  --dart-define=NEMU_PIXEL_ID=<PIXEL_ID> \
  --dart-define=NEMU_SDK_TOKEN=<SDK_TOKEN>
In Dart code, access the values with:
const pixelId = String.fromEnvironment('NEMU_PIXEL_ID');
const sdkToken = String.fromEnvironment('NEMU_SDK_TOKEN');

Initialization

Initialize the SDK as early as possible in your app lifecycle, preferably in the main() function:
import 'package:flutter/material.dart';
import 'package:nemu_attribution/nemu_attribution.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await NemuAttribution.instance.init(
    const NemuAttributionConfig(
      pixelId: String.fromEnvironment('NEMU_PIXEL_ID'),
      sdkToken: String.fromEnvironment('NEMU_SDK_TOKEN'),
      debug: true, // Disable in production
    ),
  );

  runApp(const MyApp());
}
The init() call is idempotent: calling it more than once has no effect. The SDK initializes only on the first call.

User identification

After login, register the user’s unique identifier to associate the attribution with the profile:
import 'package:nemu_attribution/nemu_attribution.dart';

Future<void> signIn(String email, String password) async {
  final user = await authApi.login(email: email, password: password);

  await NemuAttribution.instance.setUserId(user.id);
}
For already authenticated sessions (e.g., when opening the app with a saved token):
import 'package:nemu_attribution/nemu_attribution.dart';

Future<void> bootstrapSession() async {
  final user = await sessionManager.loadUser();

  if (user != null) {
    await NemuAttribution.instance.setUserId(user.id);
  }
}

Getting UTMs from the session

Access the UTMs from the last captured attribution:
import 'package:nemu_attribution/nemu_attribution.dart';

Future<void> getUtms() async {
  final attribution = await NemuAttribution.instance.getAttribution();

  print('utm_source: ${attribution?.source}');
  print('utm_medium: ${attribution?.medium}');
  print('utm_campaign: ${attribution?.campaign}');
  print('utm_content: ${attribution?.content}');
  print('utm_term: ${attribution?.term}');
}

Sending purchase data with UTMs

When sending purchase data to the Nemu API, include the captured UTMs:
import 'package:nemu_attribution/nemu_attribution.dart';

Future<Map<String, dynamic>> buildPurchasePayload() async {
  final attribution = await NemuAttribution.instance.getAttribution();

  return {
    'transactionId': '123',
    'netValue': 99.9,
    'status': 'paid',
    'utm_source': attribution?.source,
    'utm_medium': attribution?.medium,
    'utm_campaign': attribution?.campaign,
    'utm_content': attribution?.content,
    'utm_term': attribution?.term,
  };
}

If you need to process a URL manually (e.g., received by another mechanism):
final result = await NemuAttribution.instance.captureDeepLink(
  'https://app.seudominio.com?utm_source=google&utm_medium=cpc&utm_campaign=verao',
);

if (result != null) {
  print('Captured attribution: ${result.source}');
}

Debug mode

Enable debug mode during development to verify the integration through console logs:
await NemuAttribution.instance.init(
  const NemuAttributionConfig(
    pixelId: 'YOUR_PIXEL_ID',
    debug: true, // Enable detailed logs
  ),
);
Logs are displayed with the prefix [NemuAttribution] and can be viewed in DevTools or in your terminal.
Disable debug mode in production.

The SDK automatically captures:
  • Cold-start deep links - when the app is opened via a link (app is closed)
  • Warm-start deep links - when the app receives a link while already open
  • Deferred deep links - preserves UTM data even when the user installs the app after clicking the link
To enable deferred deep links, configure the endpoint during initialization:
await NemuAttribution.instance.init(
  const NemuAttributionConfig(
    pixelId: 'YOUR_PIXEL_ID',
    sdkToken: 'YOUR_SDK_TOKEN',
    deferredDeepLinkUrl: 'https://api.nemu.com.br/deferred',
  ),
);

Attribution flow

First access (first install):
  1. Deep link (cold-start) -> if it has UTMs, use them as first-touch
  2. Deferred deep link -> if configured, try to resolve
  3. Organic -> fallback (source: "organic", medium: "none")

Subsequent accesses:
  - Deep links update only the last-touch
  - First-touch is immutable after the first installation
  - Last-touch respects the configured TTL (default: 30 days)