# Add device fingerprinting to a Flutter app

> Add device fingerprinting to a Flutter app with a typed Dart API over the native iOS and Android SDKs, then verify the result on your server.

Last updated: 2026-11-12

This tutorial adds **device fingerprinting** to a Flutter app. `fingerly_flutter` is a plugin over the native iOS and Android SDKs: collection happens in native code, and Dart gets typed results. The server returns a visitor ID and a suspect score, and your backend decides what to do.

## Before you start

- Flutter 3.10 or newer with Dart 3, and iOS 13 and Android 5.0 (API 21) or newer.
- A public key for the development environment, and a secret key for your server.
- A decision about consent: the SDK collects nothing until `consent` is `ConsentState.granted`.

## Add the SDK and verify the result

### Step 1: Create your keys

In the Fingerly dashboard, open **Integration > SDK keys** and create a **public** key in the **development** environment, and a **secret** key in the same environment for your server. Development keys run the same detection as production and cost nothing. A production key must also name your app, an Android package or an Apple app ID, as in [API keys and environments](https://docs.fingerly.io/docs/api-keys#native-apps).

### Step 2: Add the plugin

```bash Terminal
flutter pub add fingerly_flutter
```

On iOS, the plugin fetches the native framework on the first build and checks it against a pinned checksum, through Swift Package Manager or CocoaPods, whichever your app uses. On Android, it adds Fingerly's Maven repository to the build itself.

### Step 3: Load the client and identify at sign-in

```dart lib/fingerly.dart
import 'package:fingerly_flutter/fingerly_flutter.dart';

// consent: granted only once you have consent (or another lawful basis)
final fingerly = await Fingerly.load(apiKey: 'fly_pk_development_…', consent: ConsentState.granted);

Future<void> signIn(String email, String password) async {
  final result = await fingerly.identify(tag: 'sign-in');
  await api.signIn(email, password, requestId: result.requestId);
}
```

`suspectScore` and `suspectLevel` are `null` when absent, and `visitorIsNew`, `visitorConfidence`, `identifiable` and `duplicate` are nullable too. With `submit: false`, only `report` is filled in.

### Step 4: Verify on your server

A phone is a device you do not control, so the decision is never made in the app. Send the `requestId` to your backend with the action, read the stored event with your secret key, and check it belongs to this action and is recent.

```ts Sign-in handler (Node.js)
import { FingerlyAPIError, load } from '@fingerly/node'

const fingerly = load({ secretKey: process.env.FINGERLY_SECRET_KEY! })

export async function afterPasswordCheck(requestId: unknown) {
  if (typeof requestId !== 'string' || requestId === '') return 'second-factor' // missing evidence

  try {
    const event = await fingerly.events.get(requestId)
    if (event.tag !== 'sign-in') return 'second-factor'
    if (Date.now() - Date.parse(event.occurred_at) > 2 * 60 * 1000) return 'second-factor'
    return event.suspect_level === 'high' ? 'second-factor' : 'allow'
  } catch (error) {
    if (error instanceof FingerlyAPIError && error.code === 'event_not_found') return 'second-factor'
    throw error // still pending: answer with a retry, never a refusal
  }
}
```

Also check the request ID has not been used before, and answer a pending event with a retry rather than a refusal. [Server-side verification](https://docs.fingerly.io/docs/server-side-verification) explains each check, and the [use-case recipes](https://docs.fingerly.io/docs/use-cases) include a helper that does all of them.

### Step 5: Handle errors

```dart errors.dart
try {
  await fingerly.identify();
} on FingerlyException catch (error) {
  if (error.status == 401) { /* the key is wrong */ }
  if (error.status == 402) { /* the organization is not accepting traffic */ }
}
```

Until consent is `granted`, `identify` and `collect` throw `FingerlyConsentException`. If identification fails for any reason, let the user continue and send no request ID.

### Step 6: Run it on both platforms

Run the app on an iOS and an Android target, sign in, then open **Identification > Events** in the dashboard to see the visitor, score and signals for each.

## Consent and erasure

Load with the default `ConsentState.pending` and call `setConsent` from your consent tool, or pass `granted` once you have consent or another lawful basis. The consent state is shared by the whole app, so pass it on every `load`.

```dart lib/delete_account.dart
await Fingerly.deleteStoredIdentity();
```

This deletes the install record on the device, for erasure requests and account deletion. See [account deletion and erasure](https://docs.fingerly.io/docs/app-store-privacy#account-deletion-and-erasure).

> **Tip:** On iOS the identity survives the app being deleted, and on Android it does not. A reinstalled Android app gets a new visitor ID. See [visitor identification](https://docs.fingerly.io/docs/visitor-identification#in-apps).

## Common questions

### Does the plugin fingerprint in Dart?

No. Collection happens in the native iOS and Android SDKs, and Dart receives typed results. The same visitor ID rules apply as in a native app.

### Which platforms does the plugin support?

iOS 13 and newer and Android 5.0 (API 21) and newer, with Flutter 3.10 or newer. Never use the browser SDK inside an app's web view: it collects nothing there and the answer has `identifiable: false`.

### What is the identity on a Flutter device?

A random install ID the native SDK keeps, never a hardware identifier. It lives in the Keychain on iOS and in a file excluded from backups on Android.

### How do I read the native SDK version?

Call `Fingerly.nativeVersion()`, which returns a `Future<String>`.

### Can I decide in Dart whether to allow the action?

You can adapt the interface, for example to add a step when `suspectLevel` is `high`. The decision itself belongs on your backend, from the stored event, because an app can be modified.

- [Flutter SDK](https://docs.fingerly.io/docs/sdks/flutter): Every member, field and error.
- [React Native tutorial](https://docs.fingerly.io/docs/device-fingerprinting-react-native): The same shape in TypeScript.
- [Mobile app attestation](https://docs.fingerly.io/docs/mobile-attestation): Proof a report comes from your genuine app.
- [Server-side verification](https://docs.fingerly.io/docs/server-side-verification): The four checks before you trust a result.
