Skaletek KYC Flutter Plugin
A comprehensive Flutter plugin for Know Your Customer (KYC) verification services, featuring document scanning, face liveness detection, and identity verification powered by AWS Amplify.
β¨ Features
- π Document Verification: Passport, National ID, Driver's License, and more
- π€ Face Liveness Detection: Real-time biometric verification using AWS Amplify
- πΈ Camera Integration: Live document capture with auto-detection
- π¨ Customizable UI: Branded verification experience
- π Secure: Enterprise-grade security with AWS infrastructure
- π± Cross-platform: iOS and Android support
π Quick Start
1. Installation
2. Platform Setup
π± Android Setup
Requires Kotlin 2.2.0.
Step 1: Update Project Build Configuration
1.1. Kotlin Version (Project Level)
Set Kotlin to 2.2.0 or higher. Check android/settings.gradle(.kts) for a
plugins { } block containing version numbers:
- Present β set the Kotlin version there. Projects created with recent Flutter versions already declare 2.3.20 and need no change.
- Absent β add the
buildscriptblock below toandroid/build.gradle.
Groovy β android/build.gradle
buildscript {
ext.kotlin_version = '2.2.0'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.9.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Kotlin DSL β android/build.gradle.kts
buildscript {
extra.apply {
set("kotlin_version", "2.2.0")
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.9.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.0")
}
}
1.2. Update android/app/build.gradle (App Level)
Add one line at the bottom, after the android { } and flutter { } blocks. It applies
the SDK's build settings β minSdk, core library desugaring, and the compileSdk its
dependencies require.
Groovy β android/app/build.gradle
Kotlin DSL β android/app/build.gradle.kts
Step 2: Permissions (Automatic)
INTERNET and CAMERA are declared in the SDK's manifest and merge into your app.
NFC comes with the optional skaletek_kyc_nfc package (see NFC).
Nothing to add unless you customise manifest merging.
Step 3: Update MainActivity
Ensure your MainActivity extends FlutterFragmentActivity:
// android/app/src/main/kotlin/com/yourpackage/yourapp/MainActivity.kt
package com.yourpackage.yourapp
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity()
π iOS Setup
Step 1: Deployment Target
Set the iOS deployment target to 14.0.
In Xcode: select the Runner target β General β Minimum Deployments β iOS 14.0.
Or in code: set all three occurrences in ios/Runner.xcodeproj/project.pbxproj
(Debug, Release, Profile):
Step 2: Permissions
Add to ios/Runner/Info.plist. iOS terminates the app without it:
<key>NSCameraUsageDescription</key>
<string>This app needs camera access for document scanning and face verification.</string>
Step 3: NFC (optional)
NFC chip reading ships as a separate package, so apps that don't use it carry no NFC code or permissions. To offer it, add:
There's no code to write. With the package installed, Passport and National ID flows show the NFC option on supported devices; pass enableNfc: false on KYCCustomization to hide it for a session. Without it, those flows use document upload only and you can skip the rest of this step.
iOS (NFC)
Add the usage description to ios/Runner/Info.plist:
<key>NFCReaderUsageDescription</key>
<string>This app uses NFC to read your passport chip for identity verification.</string>
Then enable the capability once in Xcode:
- Open
ios/Runner.xcworkspacein Xcode. - Select the Runner target β Signing & Capabilities tab.
- Click + Capability and add Near Field Communication Tag Reading.
This automatically creates (or updates) ios/Runner/Runner.entitlements:
Note: NFC capability requires an Apple Developer account and a real device β NFC is not available in the iOS Simulator.
3. Add ISO 7816 application identifiers (required for e-passport reading)
This step is critical β without it the NFC session can start but immediately time out without detecting the chip.
Add the following to ios/Runner/Info.plist (not the entitlements file):
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>A0000002471001</string>
<string>A0000002472001</string>
<string>00000000000000</string>
</array>
These are the standard ICAO 9303 Application Identifiers used by e-passports. Place them alongside NFCReaderUsageDescription in Info.plist.
NFC troubleshooting
- "Session timeout" / chip not detected: Ensure
com.apple.developer.nfc.readersession.iso7816.select-identifierswith the three AIDs is inInfo.plist(not the entitlements file). This is the most common cause of NFC sessions opening but immediately failing. - "Failed to connect to NFC chip": Confirm Near Field Communication Tag Reading capability is added in Xcode under Signing & Capabilities, and that
Runner.entitlementscontainscom.apple.developer.nfc.readersession.formats = [TAG]. - Authentication failed after the chip is detected: Check that the document number, date of birth, and expiry date match the MRZ exactly. Wrong MRZ key fields can feel like NFC detection failure because the chip rejects BAC/PACE authentication.
- Android detection is inconsistent: Remove thick or metal cases, place the document on a flat surface, and keep the phone still for up to 25 seconds while the reader finds the chip antenna.
- NFC only works on physical devices β not supported in the iOS Simulator.
π API Reference
KYCUserInfo
final userInfo = KYCUserInfo(
firstName: "John",
lastName: "Doe",
documentType: DocumentType.passport.value,
issuingCountry: "USA",
);
KYCCustomization
final customization = KYCCustomization(
docSrc: DocumentSource.camera.value,
partnerName: "Your Company",
logoUrl: "https://example.com/logo.png", // optional
primaryColor: Colors.blue, // optional
enableNfc: true, // optional; false = hide NFC. Needs skaletek_kyc_nfc
);
Document Types
| Type | Description |
|---|---|
DocumentType.passport |
International passport |
DocumentType.nationalId |
National ID card |
DocumentType.driverLicense |
Driver's license |
DocumentType.residencePermit |
Residence permit |
DocumentType.healthCard |
Health/medical card |
Document Sources
| Source | Description |
|---|---|
DocumentSource.camera |
Live camera capture with auto-detection |
DocumentSource.file |
File upload from device gallery |
π Environment Configuration
You can now specify the environment for the KYC verification process. This controls which backend endpoints are used for the session.
Supported Environments
SkaletekEnvironment.devSkaletekEnvironment.prodSkaletekEnvironment.sandbox
Usage
SkaletekKYC.instance.startVerification(
context: context,
token: "your-token-here",
userInfo: userInfo,
customization: customization,
environment: SkaletekEnvironment.prod, // or .dev, .sandbox
onResult: (result) {
// Handle result
},
);
- If you do not specify the
environmentparameter, it defaults toSkaletekEnvironment.dev.
Note:
- The environment parameter is available in the
KYCConfigand is passed through the SDK automatically. - The correct API endpoints are selected internally based on the environment you choose.
π Languages
Available in English, French, Spanish, German and Portuguese, with an in-flow language dropdown. The face-liveness screen picks its language differently per platform:
- iOS follows the in-app dropdown.
- Android follows the device language.
Verification result (onResult)
The callback receives a typed [KYCResult](lib/src/models/kyc_result.dart): success
(bool), status (KYCStatus?), message and errorCode.
KYCStatus values: success, failure, awaitReview (manual review pending β handle
separately from failure), cancelled, inProgress, pending, completed, reject.
onComplete, which receives a Map<String, dynamic>, still works but is deprecated and
will be removed in 1.0.0.
Complete Example
import 'package:flutter/material.dart';
import 'package:skaletek_kyc/skaletek_kyc.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Skaletek KYC Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1261C1)),
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool _isVerifying = false;
KYCResult? _result;
Future<void> _startVerification() async {
setState(() {
_isVerifying = true;
_result = null;
});
await SkaletekKYC.instance.startVerification(
context: context,
// Create the session token on your own backend. Never ship your API key
// inside the app.
token: 'your-token-here',
userInfo: KYCUserInfo(
firstName: 'Whyte',
lastName: 'Peter',
documentType: DocumentType.passport.value,
issuingCountry: 'USA',
),
customization: KYCCustomization(
docSrc: DocumentSource.file.value,
partnerName: 'Your Company',
),
environment: SkaletekEnvironment.dev,
onResult: (result) => setState(() {
_isVerifying = false;
_result = result;
}),
);
}
/// `AWAIT_REVIEW` means the session finished but a person still has to approve
/// it β treat it as its own outcome rather than a failure.
bool get _isUnderReview => _result?.status == KYCStatus.awaitReview;
Color get _resultColor {
if (_result?.success ?? false) return Colors.green;
return _isUnderReview ? Colors.amber.shade800 : Colors.red;
}
String get _resultText {
final result = _result!;
return [
if (result.success)
'Verification complete'
else if (_isUnderReview)
'Under review'
else
'Verification failed (${result.status?.value ?? 'unknown'})',
if (result.message?.isNotEmpty ?? false) result.message!,
if (result.errorCode?.isNotEmpty ?? false)
'Error code: ${result.errorCode}',
].join('\n');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Skaletek KYC')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.verified_user, size: 80, color: Color(0xFF1261C1)),
const SizedBox(height: 24),
const Text(
'Skaletek KYC SDK Demo',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 32),
if (_isVerifying)
const CircularProgressIndicator()
else
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: _startVerification,
child: const Text('Start Identity Verification'),
),
),
if (_result != null) ...[
const SizedBox(height: 24),
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: _resultColor),
borderRadius: BorderRadius.circular(8),
),
child: Text(
_resultText,
textAlign: TextAlign.center,
style: TextStyle(color: _resultColor),
),
),
],
],
),
),
);
}
}
π§ Troubleshooting
Common Issues
Android build errors:
| Error | Cause | Fix |
|---|---|---|
This version of the Compose Compiler requires Kotlin version β¦ |
Kotlin version differs from the one the liveness plugin pins | Set kotlin_version to 2.2.0 |
The Kotlin Gradle plugin was loaded multiple times⦠|
Same cause, reported as a warning | Align Kotlin versions across settings.gradle and build.gradle |
Plugin request for plugin already on the classpath must not include a version |
Versions declared in both settings.gradle and a buildscript block |
Declare them in one place β see Step 1.1 |
E/GeneratedPluginRegistrant: Error registering plugin face_liveness_detector |
Expected. The SDK configures AWS at runtime instead of at build time | None β the app is unaffected |
iOS build errors:
- Verify the iOS deployment target is 14.0 or higher
Plugin "SmithyCodeGeneratorPlugin" from package "smithy-swift" must be enabled before it can be used means the Amplify Swift packages resolved above the pinned versions. In Xcode,
File β Packages β Reset Package Caches, then rebuild.
Face liveness:
- Verify camera permissions are granted
- Check network connectivity for AWS services
NFC: see Step 3 for entitlements and provisioning.