Building a KYC/Liveness Detection Flow in Flutter Without Lock-in

Building a KYC/Liveness Detection Flow in Flutter Without Third-Party Lock-in

If you followed the Flutter + Supabase auth flow from this blog, this is the natural next step for any app handling money, deliveries, or regulated services: proving the person behind the account is a real, present human, not a stolen photo or a stock image.

Most tutorials on this topic jump straight to a paid KYC vendor’s SDK. That’s often the right call at scale, but it’s not the right call on day one — you don’t want a per-verification bill before you have users to verify. This is the architecture I use: start with a free, open-source liveness check, and design the rest of the flow so swapping in a paid provider later is a config change, not a rewrite.

The two things “identity verification” actually means

It’s worth separating these clearly, because they solve different problems and get conflated constantly:

  • Liveness detection: proving the person in front of the camera right now is a real, present human — not a photo of a photo, not a video replay, not a static image. This is what stops the most common, low-effort fraud.
  • Document verification / face match: confirming the ID document is genuine and that the face on it matches the live selfie. This is a separate, harder problem — usually solved by OCR plus a matching model.

A lot of early-stage apps implement only document capture (photo of ID + a selfie) with no liveness check at all. That’s better than nothing, but it’s trivially defeated by holding up a printed photo or a second phone playing a video. If you’re going to build one piece first, make it liveness.

Phase 1: free and open-source

For launch and early testing, a package like flutter_liveness_check covers the core liveness flow — prompting the user through actions (blink, turn head, smile) and using the front camera to confirm a live response — without any per-verification cost. This matters enormously in the phase before you know your actual verification volume: paying per check before you have users to check is money spent on nothing.

dependencies:
  flutter_liveness_check: ^latest
  camera: ^latest

The rough integration shape:

final result = await LivenessCheck.start(
  context: context,
  actions: [LivenessAction.blink, LivenessAction.turnLeft, LivenessAction.smile],
);

if (result.success) {
  final selfiePath = result.capturedImagePath;
  // proceed to document capture / upload
} else {
  // show retry with a clear reason, not a generic failure
}

Pair this with a straightforward document capture step (photo of the ID document, with basic edge-detection cropping) and you have a functional, zero-cost KYC flow suitable for launch and early volume.

Design the interface, not just the screen

This is the part that actually saves you a rewrite later: define a small internal interface for “verify this user,” and have your Phase 1 implementation satisfy it, rather than calling the liveness package directly from your UI code.

abstract class IdentityVerificationProvider {
  Future<VerificationResult> verify({
    required String userId,
    required String selfiePath,
    required String documentPath,
  });
}

class LocalLivenessProvider implements IdentityVerificationProvider {
  @override
  Future<VerificationResult> verify({
    required String userId,
    required String selfiePath,
    required String documentPath,
  }) async {
    // liveness already confirmed by the capture flow; store the pair
    // for manual review, or run basic OCR if you have that piece too
    return VerificationResult.pendingManualReview();
  }
}

Every screen in your app that needs to know “is this user verified” depends on IdentityVerificationProvider, never on the specific package. When you’re ready to move to a paid vendor, you write one new class that implements the same interface and swap it in behind a feature flag — the UI, the state management, the rest of your app, none of it changes.

Phase 2: when to move to a paid provider

The free/open-source path is right for launch, not forever. Move to a paid, purpose-built KYC provider once verification volume is real enough that false positives/negatives and fraud attempts start costing more than the per-check fee would. For products operating in African markets specifically, providers like Smile ID are worth evaluating — they cover a wide range of national ID formats and have local document-verification models that a generic open-source liveness package simply doesn’t attempt.

Two things to nail down before switching:

  • Data continuity: decide up front what you store from Phase 1 (raw selfie/document images, or just a verification status), so migrating verified users doesn’t force a re-verification wave.
  • Fallback behavior: paid providers can have outages or rate limits too. Keep the interface flexible enough to route to a manual review queue if the primary provider is unavailable, rather than blocking signups entirely.

Storage and privacy basics that are easy to skip

  • Don’t store raw ID images longer than necessary. If your paid provider (or your own review process) only needs them transiently, delete after verification completes, and keep only the verification result and a hash/reference.
  • Encrypt at rest whatever you do keep — this is sensitive personal data by any regulatory definition, not just “a photo.”
  • Log verification attempts, not verification content. Your debugging logs should never contain a base64-encoded selfie.

Where this fits with the rest of the stack

This slots in right after the auth flow: a new user signs up, hits an AuthGate that also checks verification status, and is routed to the liveness flow if unverified. The IdentityVerificationProvider abstraction means this entire subsystem can start as a zero-cost, open-source implementation and graduate to a production KYC vendor without touching a single screen that isn’t directly about verification — which is exactly the kind of decision that’s cheap to make right early and expensive to unwind later.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top