Guides

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.

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

  1. 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.

  2. Step 2: Add the plugin

    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.

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

    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.

  4. 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.

    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 explains each check, and the use-case recipes include a helper that does all of them.

  5. Step 5: Handle errors

    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.

  6. 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.

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.

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.

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.