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:
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,
};
}
Manual deep link capture
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.
Deep links and deferred deep links
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)