kreltix_liveness_sdk 0.2.2 copy "kreltix_liveness_sdk: ^0.2.2" to clipboard
kreltix_liveness_sdk: ^0.2.2 copied to clipboard

Kreltix liveness detection SDK for Flutter — challenge-based, confidence-scored, drop-in ready.

kreltix_liveness_sdk #

Flutter SDK for Kreltix — identity verification with three methods to fit any flow.

Method What it does Camera needed?
startLivenessCheck() Challenge-based liveness detection — SDK handles camera, animated overlay, recording Yes
startCombinedCheck() Liveness + face match — SDK handles camera, challenges, and recording; you provide the ID card image Yes
faceMatch() Compare ID card vs selfie — no camera, no session No

Features #

  • Drop-in liveness overlay — randomised blink/turn/smile challenges, no UI work required
  • Direct face match — compare two images without touching the camera
  • Combined check — single call confirms liveness AND face match simultaneously
  • Confidence-scored results with per-challenge breakdown
  • Progress callbacks at every stage of the liveness flow
  • Typed error hierarchy — catch exactly the error you care about
  • iOS and Android support (Flutter 3.10+, Dart 3.0+)

Installation #

Add to your pubspec.yaml:

dependencies:
  kreltix_liveness_sdk: ^0.1.0

Then run:

flutter pub get

Permissions #

These entries are required. Without them the app will crash on first camera access.

Android #

android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

iOS #

ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>Camera is required for liveness verification.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is required for video recording.</string>

Both keys are needed — the SDK records short video clips during each challenge.


Quick Start #

import 'package:kreltix_liveness_sdk/kreltix_liveness_sdk.dart';

// Initialise once — reuse the instance across your app.
final sdk = KreltixSDK.initialize('pk_live_your_public_key');

// Inside any widget method where you have a BuildContext:
final result = await sdk.startLivenessCheck(
  context: context,
  metadata: {'userId': 'user_123'},
);

if (result.isLive) {
  print('Verified — ${result.confidence.toStringAsFixed(0)}% confidence');
} else {
  print('Failed: ${result.recommendation}');
}

The SDK pushes a fullscreen MaterialPageRoute overlay onto your navigator stack, runs all challenges, shows the result screen for 2.5 seconds, then pops automatically — your await resumes with the LivenessResult.


Face Match #

Compare an ID card against a selfie without any camera or session. Pass plain base64 strings.

final result = await sdk.faceMatch(
  idCardImage: idCardBase64,
  selfieImage: selfieBase64,
  metadata: {'userId': 'user_123'},
);

if (result.verified) {
  print('Match confirmed — distance: ${result.distance.toStringAsFixed(3)}');
} else {
  print('No match: ${result.status}');
}

FaceMatchResult fields #

Field Type Description
requestId String? Server-assigned request ID for audit logs
match bool Raw face match result
distance double Euclidean distance — lower is more similar (0.0–1.0)
verified bool true when distance is within the confidence threshold
status String "verified", "mismatch", or "error"
error String? Error detail if the check could not complete

Combined Check — Liveness + Face Match #

The SDK handles everything — camera, challenge selection, recording, and upload. You only provide the ID card image as the reference face. The server picks random challenges, the overlay guides the user, and the best frame from the recordings is automatically matched against the ID card.

final result = await sdk.startCombinedCheck(
  context: context,
  idCardImage: idCardBase64,    // base64 string or https:// URL
  metadata: {'userId': 'user_123'},
);

if (result.verified == true) {
  print('Verified — live person and face matches the ID card');
} else if (result.isLive && result.faceMatch == false) {
  print('Liveness passed but face did not match');
} else {
  print('Liveness failed: ${result.recommendation}');
}

The same optional callbacks as startLivenessCheck are available:

final result = await sdk.startCombinedCheck(
  context: context,
  idCardImage: idCardBase64,
  onSessionCreated: (session) => print('Challenges: ${session.challenges}'),
  onChallengeStarted: (challenge) => print('Next: $challenge'),
  onProgress: (event) => print('[${event.stage.name}] ${event.message}'),
  onError: (err) => print('Error: $err'),
);

CombinedLivenessResult fields #

Field Type Description
sessionId String Session ID — use to correlate with webhook events
isLive bool Liveness passed all challenges
livenessConfidence double 0–100 liveness confidence score
allChallengesPassed bool Every individual challenge completed
challengeResults List<ChallengeStepResult> Per-challenge breakdown
faceChecked bool Whether face match ran (only runs when liveness passes)
faceMatch bool? Face match result — null when faceChecked is false
faceDistance double? Euclidean distance from face comparison
verified bool? true only when both liveness AND face match passed
recommendation String? Human-readable outcome message
error String? Error detail if either check failed
indicators Map<String, dynamic>? Raw liveness analysis signals

How It Works #

Your app            SDK overlay             Kreltix API
   │                     │                      │
   │─ initialize() ─────▶│                      │
   │─ startLivenessCheck()                      │
   │                     │─ POST /session ──────▶│
   │                     │◀─ challenges ─────────│
   │                     │                      │
   │      [overlay pushed onto navigator]       │
   │                     │                      │
   │                     │  challenge 1: blink  │
   │                     │  [3-sec recording]   │
   │                     │  challenge 2: smile  │
   │                     │  [3-sec recording]   │
   │                     │                      │
   │                     │─ POST /verify ───────▶│
   │                     │◀─ LivenessResult ─────│
   │                     │                      │
   │      [result screen shown 2.5s, overlay popped]
   │◀─ LivenessResult ───│

All Options #

startLivenessCheck accepts named parameters beyond the required context:

final result = await sdk.startLivenessCheck(
  context: context,

  // Optional metadata — echoed back in the webhook payload
  metadata: {'userId': 'user_123', 'source': 'flutter-app'},

  // Fires when the backend session is created (contains challenge list)
  onSessionCreated: (session) {
    print('Challenges: ${session.challenges.join(' → ')}');
    print('Expires: ${session.expiresAt}');
  },

  // Fires before each challenge recording starts
  onChallengeStarted: (challenge) {
    print('Next challenge: $challenge'); // e.g. "blink", "turn_left"
  },

  // Fires at each stage of the flow
  onProgress: (event) {
    print('[${event.stage.name}] ${event.message}');
  },

  // Fires if an error occurs (the exception is also rethrown)
  onError: (err) {
    print('SDK error: $err');
  },
);

SDK Options #

Pass SDKOptions as the second argument to initialize() to customise networking behaviour:

final sdk = KreltixSDK.initialize(
  'pk_live_your_key',
  SDKOptions(
    baseUrl: 'https://api.kreltix.com', // default
    timeout: Duration(seconds: 10),     // default
    maxRetries: 2,                      // default
  ),
);
Field Type Default Description
baseUrl String https://api.kreltix.com API base URL
timeout Duration Duration(seconds: 10) Per-request timeout
maxRetries int 2 Retry attempts on network failure

LivenessResult #

The object returned (and awaited) from startLivenessCheck():

Field Type Description
isLive bool true when the person passed all challenges
confidence double 0–100 liveness confidence score
allChallengesPassed bool true if every individual challenge completed
sessionId String Unique ID — use this to look up the result server-side
challengeResults List<ChallengeStepResult> Per-challenge breakdown
recommendation String? Human-readable outcome message
error String? Error message if the check could not complete
indicators Map<String, dynamic>? Raw analysis signals from the backend

ChallengeStepResult #

Each entry in challengeResults:

Field Type Description
challenge String Challenge name — "blink", "turn_left", etc.
completed bool Whether this specific challenge passed
indicators Map<String, dynamic>? Per-frame signals for this challenge

Progress Stages #

The onProgress callback fires with a ProgressEvent(stage, message) at each stage:

ProgressStage When
sessionCreated Backend session created; challenge list received
cameraReady Camera initialised and preview started
recording Fired before each challenge recording begins
uploading All videos recorded; upload starting
processing Upload complete; backend analysing videos
complete Result received; overlay closing
onProgress: (event) {
  switch (event.stage) {
    case ProgressStage.sessionCreated:
      showSnackBar('Session ready');
    case ProgressStage.recording:
      showSnackBar(event.message); // "Challenge: blink"
    case ProgressStage.complete:
      showSnackBar('Done');
    default:
      break;
  }
},

Error Handling #

All SDK errors extend KreltixError which implements Exception. Catch specific types for fine-grained recovery:

try {
  final result = await sdk.startLivenessCheck(context: context);
} on KreltixCameraError catch (e) {
  // Camera permission denied or hardware unavailable
  showDialog(context: context, builder: (_) => AlertDialog(
    title: const Text('Camera required'),
    content: Text(e.message),
  ));
} on KreltixSessionExpiredError {
  // Session timed out — safe to retry immediately
  _startCheck(); // call your method again
} on KreltixInsufficientFundsError {
  // Wallet balance too low
  launchUrl(Uri.parse('https://kreltix.com/dashboard'));
} on KreltixAuthError catch (e) {
  // Invalid or revoked pk_live_ key
  debugPrint('Auth error: ${e.message}');
} on KreltixNetworkError catch (e) {
  // Connectivity issue; SDK already retried maxRetries times
  debugPrint('Network: ${e.message} (retryable: ${e.isRetryable})');
} on KreltixError catch (e) {
  // Catch-all for any other SDK error
  debugPrint('[${e.code}] ${e.message}');
}

Error Reference #

Class Code Retryable Cause
KreltixCameraError CAMERA_ERROR No Camera permission denied or device issue
KreltixSessionExpiredError SESSION_EXPIRED No Session timed out — start a new check
KreltixAuthError AUTH_ERROR No Invalid or revoked public key
KreltixInsufficientFundsError INSUFFICIENT_FUNDS No Wallet balance too low
KreltixNetworkError NETWORK_ERROR Yes Connectivity issue; already auto-retried
KreltixServerError SERVER_ERROR Yes Temporary backend error
KreltixValidationError VALIDATION_ERROR No Invalid request parameters

All errors expose .message, .code, and .isRetryable.


Full Widget Example #

A complete StatefulWidget that tracks status and displays the result:

import 'package:flutter/material.dart';
import 'package:kreltix_liveness_sdk/kreltix_liveness_sdk.dart';

// Initialise once at app scope
final _sdk = KreltixSDK.initialize('pk_live_your_public_key');

class LivenessCheckButton extends StatefulWidget {
  const LivenessCheckButton({super.key});

  @override
  State<LivenessCheckButton> createState() => _LivenessCheckButtonState();
}

class _LivenessCheckButtonState extends State<LivenessCheckButton> {
  bool _loading = false;
  String _status = '';
  LivenessResult? _result;

  Future<void> _run() async {
    setState(() {
      _loading = true;
      _status = 'Initializing...';
      _result = null;
    });

    try {
      final result = await _sdk.startLivenessCheck(
        context: context,
        metadata: {'userId': 'user_123', 'source': 'flutter-app'},
        onSessionCreated: (s) => setState(() =>
          _status = 'Challenges: ${s.challenges.join(' → ')}'),
        onChallengeStarted: (c) => setState(() =>
          _status = 'Challenge: $c'),
        onProgress: (e) => setState(() =>
          _status = '[${e.stage.name}] ${e.message}'),
        onError: (err) => setState(() =>
          _status = 'Error: $err'),
      );

      setState(() {
        _result = result;
        _status = 'Complete.';
      });
    } on KreltixCameraError catch (e) {
      setState(() => _status = 'Camera error: ${e.message}');
    } on KreltixSessionExpiredError {
      setState(() => _status = 'Session expired — try again.');
    } on KreltixError catch (e) {
      setState(() => _status = '[${e.code}] ${e.message}');
    } finally {
      setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final result = _result;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        ElevatedButton(
          onPressed: _loading ? null : _run,
          child: Text(_loading ? 'Verifying...' : 'Verify Identity'),
        ),
        if (_status.isNotEmpty) ...[
          const SizedBox(height: 12),
          Text(_status, style: const TextStyle(color: Colors.white70, fontSize: 13)),
        ],
        if (result != null) ...[
          const SizedBox(height: 16),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: result.isLive ? Colors.green.shade900 : Colors.red.shade900,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  result.isLive
                    ? '✓ Verified — ${result.confidence.toStringAsFixed(0)}% confidence'
                    : '✗ Failed — ${result.recommendation ?? 'Check could not complete'}',
                  style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
                ),
                const SizedBox(height: 6),
                Text('Session: ${result.sessionId}',
                  style: const TextStyle(fontSize: 11, color: Colors.white54)),
              ],
            ),
          ),
        ],
      ],
    );
  }
}

Webhooks #

Set a webhook URL in the dashboard and Kreltix will POST a signed event payload each time a check completes. Use the sessionId from LivenessResult to correlate SDK results on your backend with incoming webhook events.

See the API Documentation tab in the dashboard for event shapes and signature verification.


Platform Requirements #

Platform Minimum version
Flutter 3.10.0
Dart SDK 3.0.0
iOS 12.0+
Android API 21+ (Android 5.0)

Get an API Key #

Sign up at kreltix.com and generate an SDK Key Pair from the API Keys page. You'll receive a pk_live_ public key to embed in your Flutter app.

New accounts include a 30-day free trial — all API calls are included at no charge, no card required.

Your secret sk_live_ key should only ever live on your backend server — never in the Flutter app.


Support #

1
likes
130
points
142
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Kreltix liveness detection SDK for Flutter — challenge-based, confidence-scored, drop-in ready.

Homepage

License

MIT (license)

Dependencies

camera, flutter, google_mlkit_face_detection, http, path_provider, permission_handler

More

Packages that depend on kreltix_liveness_sdk