SDKs / Flutter
IKYC Flutter SDK
Build a native Android or iOS verification experience while IKYC keeps provider credentials and the liveness session hand-off server-side.
1. Install safely
The package metadata in this repository is version 0.1.5. Confirm the exact version IKYC has approved and published for your environment before adding it. Do not assume local metadata proves a pub.dev release exists.
dependencies:
ikyc_flutter_sdk: <APPROVED_IKYC_FLUTTER_SDK_VERSION>flutter pub getPin the approved version. Before any upgrade, read its release notes, run your app tests, and test release builds on real Android and iOS devices. Do not vendor, fork, or patch the SDK in a merchant application.
qoreidsdk 2.1.0 to0.1.4 for the QoreID launch crash that could occur before its UI appeared. Version 0.1.5 also improves QoreID terminal-status compatibility on Android and iOS. Neither update requires additional merchant configuration or data.2. Create a native session on your application server
Your existing API, serverless function, or protected server environment is the application server in this guide. It authenticates the applicant, creates your verification attempt, and uses your secret IKYC project key. Flutter must not call this endpoint with a secret key.
POST https://<YOUR_IKYC_HOST>/api/v1/native/v1/sessions
x-api-key: <YOUR_SECRET_PROJECT_API_KEY>
Idempotency-Key: <NEW_STABLE_UUID>
Content-Type: application/json
{
"externalUserId": "your-server-side-verification-attempt-id",
"subjectRef": "a-pseudonymous-random-uuid",
"operations": ["config", "consent", "liveness:reference", "liveness:result", "verify"],
"ttlSeconds": 300
}subjectRef is compulsory when your requested operations include liveness. Generate it on your server as a pseudonymous random identifier. It must differ from your server-side externalUserId and must not contain a name, phone number, email, BVN, NIN, or other personal data.Save the native session details required by your server-side verification attempt, then return only the short-lived sessionToken and expiry to Flutter. Never return a project API key, QoreID credential, subject reference, external user ID, provider token, or raw provider payload.
3. Add the Flutter flow
The SDK exposes a small NativeTransport interface. Implement it with your approved HTTP stack. Pass SDK-provided headers through unchanged and do not log headers or request/response bodies.
import 'dart:convert';
import 'dart:io';
import 'package:ikyc_flutter_sdk/ikyc_flutter_sdk.dart';
class MerchantNativeTransport implements NativeTransport {
MerchantNativeTransport(this._client);
final HttpClient _client;
@override
Future<NativeResponse> send(NativeRequest request) async {
final outgoing = await _client.openUrl(request.method, request.uri);
request.headers.forEach(outgoing.headers.set);
if (request.body != null) outgoing.write(request.body);
final response = await outgoing.close();
return NativeResponse(
status: response.statusCode,
body: await utf8.decoder.bind(response).join(),
);
}
void dispose() => _client.close(force: true);
}Create the client and render the flow after your backend returns the short-lived native session.
final transport = MerchantNativeTransport(HttpClient());
final client = IkycNativeClient(
apiBaseUrl: Uri.parse('https://<YOUR_IKYC_HOST>'),
sessionToken: nativeSession.sessionToken,
transport: transport,
);
final controller = NativeFlowController(
client: client,
onSensitiveMediaCleanup: () async {
// Clear host-owned temporary state if your app creates any.
},
);
IkycVerificationFlow(
controller: controller,
onComplete: (result) {
// Notify your application server. It obtains the authoritative outcome.
},
onError: (error) {
// Show a safe message. Do not log tokens or response bodies.
},
onCancelled: () {
// Return to the host application flow.
},
)Use an HTTPS base URL in released apps. The native flow uses only Authorization: Bearer …; it does not use browser public-key or Origin headers.
4. Native consent is server-led by default
Configure the approved native mobile consent policy in Project → Flow → Native mobile consent. The SDK calls native /config and displays the exact active and effective consent text/version returned by IKYC. New integrations should omit consentDocument.
If policy is missing, inactive, malformed, or not yet effective, the flow stops before consent, liveness, and verification. There is no fallback wording. Existing integrations that pass consentDocument stay compatible, but their text and version must exactly match the active IKYC policy.
IkycVerificationFlow(
controller: controller,
consentDocument: NativeConsentDocument(
text: '<EXACT_ACTIVE_IKYC_CONSENT_TEXT>',
version: '<EXACT_ACTIVE_IKYC_CONSENT_VERSION>',
),
)5. QoreID liveness and final verification
The configured applicant checks use human labels such as Identity check, NIN verification, BVN verification, Facial liveness check, and Selfie face match. Internal or provider codes are not shown in the applicant UI.
At liveness, IKYC provides a private sdkSessionToken to the adapter for the immediate QoreID launch and required empty/default provider data. The adapter does not forward a customer reference, subject reference, external user ID, or IKYC liveness reference to QoreID. The provider token never appears in public callbacks, errors, logs, or request bodies.
/verify step. That final step creates the verification record.Android host setup
Use Android min SDK 23, compile/target SDK 36, Java 17, Android Gradle Plugin 8.13.2, and Kotlin 2.0.0. Add only camera permission for liveness:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />Add the QoreID Maven releases repository and JitPack:
allprojects {
repositories {
google()
mavenCentral()
maven(url = "https://repo.qoreid.com/repository/maven-releases/")
maven(url = "https://jitpack.io")
}
}Apply the QoreID Kotlin plugin workaround before its subproject is evaluated, then initialize the plugin in the host activity:
gradle.beforeProject {
if (name == "qoreidsdk") {
pluginManager.apply("org.jetbrains.kotlin.android")
}
}import android.os.Bundle
import com.qoreid.qoreidsdk.QoreidsdkPlugin
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
QoreidsdkPlugin.initialize(this)
}
}The current QoreID package declares a dynamic Android native dependency. Pin the reviewed artifact in your app build using the syntax that matches your Gradle file:
configurations.configureEach {
resolutionStrategy.force 'com.qoreid:qoreid-sdk:2.0.1'
}configurations.configureEach {
resolutionStrategy.force("com.qoreid:qoreid-sdk:2.0.1")
}-keep class com.qoreid.sdk.** { *; }Build and install a release APK on a real device before distribution. Do not enable cleartext traffic, use debug signing for release, or patch provider code to work around a build issue.
iOS host setup
Use iOS 13.0+, Flutter 3.41+, and Xcode 15+. Keep framework linkage in the Podfile, retain a UIScene host with a UINavigationController root, and add only NSCameraUsageDescription for liveness.
platform :ios, '13.0'
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end<key>NSCameraUsageDescription</key>
<string>Camera access is required for identity liveness verification.</string>QoreID’s iOS SDK is declared through the Flutter package’s Swift Package definition. Enable Flutter Swift Package Manager integration from the consuming app directory:
flutter config --enable-swift-package-manager
flutter clean
flutter pub get
flutter run -d <IOS_DEVICE_ID>If Xcode cannot find QoreIDSDK, verify the Flutter/Xcode versions, repeat those commands, and open ios/Runner.xcworkspace to let Xcode resolve packages. Do not add a manual QoreID pod or copy frameworks into the app.
Testing and controlled rollout
Use DeterministicMockLivenessAdapter only for local demos and automated tests. It does not open a camera or contact a provider. For a controlled provider test, use the approved environment, active native consent, approved test data, and a real Android/iOS test device.
config → consent → liveness reference → liveness result → verifyFor support, capture only the operation name, HTTP status, IKYC error code, URI path, and safe platform exception type. Never log or share headers, bearer tokens, API keys, request/response bodies, provider callbacks, references, or applicant data.
Troubleshooting
| Symptom | Likely cause | Safe action |
|---|---|---|
| Configuration will not load | Expired session, wrong base URL, or transport failure | Mint a new session and check operation, status, error code, and URI path only. |
| Consent unavailable | Policy is absent, inactive, malformed, future-dated, or explicit text/version differs | Activate the approved policy; omit consentDocument or make it exactly match. |
| QoreID will not open | Camera permission or host setup is incomplete | Check native permission/setup and test a release build on a real device. |
| Capture completes but no dashboard record | Final native verify step did not complete | Check the safe verify operation metadata; do not use provider UI as the final status. |
| Android works only in debug | R8 rule, native artifact, or release build difference | Keep the dependency pin and ProGuard rule; test the release APK. |
| iOS cannot resolve QoreIDSDK | Swift Package Manager has not resolved the dependency | Enable Flutter SPM, clean, run pub get, then open Runner.xcworkspace. |
Never expose bearer tokens, API keys, headers, full bodies, raw provider payloads, customer references, subject references, external user IDs, or personal data in a support request.
Security checklist and consent migration
Do
- Keep secret keys and references server-side.
- Use HTTPS in released apps.
- Let the SDK load active consent by default.
- Complete final verification before trusting the result.
Do not
- Mint sessions from Flutter.
- Expose secrets, tokens, or raw provider data.
- Send subjectRef or externalUserId from Flutter.
- Blindly retry IDEMPOTENCY_EFFECT_UNRESOLVED.
Existing apps that pass consentDocument continue to work when its text and version exactly match the active policy. New integrations should use IkycVerificationFlow(controller: controller) and let IKYC provide the approved policy.