mytm_liveness_sdk 1.0.0 copy "mytm_liveness_sdk: ^1.0.0" to clipboard
mytm_liveness_sdk: ^1.0.0 copied to clipboard

Liveness and KYC SDK for Flutter — passport and ID capture, MRZ, NFC chip reading, selfie liveness, face match, and backend verification.

MyTM Liveness & KYC SDK for Flutter #

Flutter SDK for liveness and KYC. Captures passport / ID images, reads MRZ, reads NFC chip data on supported devices, captures selfies for liveness and face‑match, and uploads everything securely to the backend for verification.

The SDK is intentionally thin. All sensitive verification logic — OCR, document liveness, face liveness, face matching, and the final decision — runs on the backend, not in the SDK, so it can't be bypassed or model‑extracted by a hostile client. The SDK also deliberately does not own the camera: your app brings its own camera plugin and feeds a live preview into the SDK's overlay widgets, then hands the captured JPEG bytes back to the SDK.


Contents #


Supported platforms #

Platform Min version Camera NFC
Android API 24 (Android 7.0) ✅ requires android.permission.NFC
iOS iOS 13.0 ✅ requires NFCReaderUsageDescription + entitlement

Installation #

dependencies:
  mytm_liveness_sdk: ^1.0.0

  # The SDK does not bundle a camera. Your app supplies the live preview:
  camera: ^0.11.0
flutter pub get

During local development you can reference the SDK by path instead:

mytm_liveness_sdk:
  path: ../sullis-kyc-platform/mobile/flutter-sdk

Platform setup #

Android #

  1. android/app/src/main/AndroidManifest.xml — camera + NFC:

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.NFC" />
    <uses-feature android:name="android.hardware.camera" android:required="true" />
    <uses-feature android:name="android.hardware.nfc"    android:required="false" />
    
  2. android/app/build.gradleminSdkVersion 24 or higher.

  3. ProGuard (release only)android/app/proguard-rules.pro:

    -keep class com.sullis.kyc.** { *; }
    
  4. Production NFC — pull in jmrtd + scuba-sc-android and implement SullisKycPlugin.readPassportInternal (see plugin source for the contract).

iOS #

  1. ios/Runner/Info.plist:

    <key>NSCameraUsageDescription</key>
    <string>Used to capture your passport and selfie for identity verification.</string>
    
    <key>NFCReaderUsageDescription</key>
    <string>Used to read your passport chip for identity verification.</string>
    
    <key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
    <array>
      <string>A0000002471001</string>
    </array>
    
  2. Entitlements — Xcode → Signing & Capabilities → + Capability → Near Field Communication Tag Reading.

  3. Deployment target — iOS 13.0 minimum.

  4. Production NFC — add pod 'NFCPassportReader' to the plugin podspec and implement readPassportInternal in SullisKycPlugin.swift.


API keys #

Keys are minted per tenant and are environment‑prefixed:

  • Sandbox: sk_test_…SullisEnvironment.sandbox
  • Live: sk_live_…SullisEnvironment.live

Never embed a live key in your shipped binary. Fetch a short‑lived key from your own backend at runtime, or have your backend mint a per‑session SDK token.


The full flow at a glance #

initialize ─▶ createSession ─▶ captureDocument(…) ─▶ captureSelfie ─▶ submitVerification
                                   │  (1+ sides,                          │
                                   │   one "attempt")                     ▼
                                   └──── optional: readAndSubmitNfc   submit API response
                                                                          (printed & returned)

A session represents one customer verification. Within a session you open one or more attempts; every artifact uploaded for a single attempt (each document side, the selfie, NFC) is verified together. You start a fresh attempt with the first document, then attach the remaining sides and the selfie to that same attempt.


Step‑by‑step integration #

1. Initialize (once at startup) #

import 'package:mytm_liveness_sdk/mytm_liveness_sdk.dart';

await SullisKycSdk.initialize(
  baseUrl:     'https://api.sullis.example.com', // no trailing /api — the SDK appends /api/v1/…
  apiKey:      keyFromYourBackend,
  environment: SullisEnvironment.sandbox,
);

2. Register callbacks (optional) #

Callbacks mirror every stage of the flow, useful for logging or driving UI state:

SullisKycSdk.instance.setCallbacks(SullisCallbacks(
  onStarted:          ()       => log('▶ started'),
  onDocumentCaptured: (d)      => log('📄 uploaded ${d.sizeBytes} bytes'),
  onSelfieCaptured:   (s)      => log('🤳 selfie ${s.sizeBytes} bytes'),
  onProcessing:       ()       => showSpinner(),
  onVerified:         (r)      => onSuccess(r),
  onRejected:         (r)      => onRejected(r.failureReason),
  onManualReview:     (caseId) => onPending(caseId),
  onError:            (e)      => log('error: $e'),
));

3. Capture with the SDK camera overlay #

The SDK ships overlay widgets (document guide, face oval, NFC prompt) but not a camera. Compose the SDK's CaptureScreen with a live CameraPreview from the camera plugin, let the user frame the target, grab the frame, and return its JPEG bytes. See example/lib/screens/camera_capture_screen.dart for a complete, copy‑pasteable capture screen. It pops with a Uint8List (or null if cancelled):

final Uint8List? bytes = await Navigator.of(context).push<Uint8List>(
  MaterialPageRoute(
    builder: (_) => CameraCaptureScreen(mode: CaptureMode.passportTd3, title: 'Passport'),
    fullscreenDialog: true,
  ),
);

4. Create a session #

final session = await SullisKycSdk.instance.createSession(
  customerReference: 'CUSTOMER-${user.id}',
  webhookUrl:        'https://api.yourapp.com/sullis/webhook', // optional
);
// createSession also refreshes per-tenant config (NFC on/off, branding, allowed doc types).

5. Upload documents — attempt grouping #

Pass the bytes you captured in step 3. The first document of a verification must pass startNewAttempt: true; additional sides pass false so they attach to the same attempt.

A passport is a single page:

await sdk.captureDocument(
  sessionId:    session.sessionId,
  documentType: 'PASSPORT',
  documentSide: 'FRONT',
  imageBytes:   passportBytes,
  // startNewAttempt defaults to true
);

A national ID card is captured on both sides — same attempt:

await sdk.captureDocument(
  sessionId: session.sessionId, documentType: 'NATIONAL_ID', documentSide: 'FRONT',
  startNewAttempt: true,  imageBytes: idFrontBytes,
);
await sdk.captureDocument(
  sessionId: session.sessionId, documentType: 'NATIONAL_ID', documentSide: 'BACK',
  startNewAttempt: false, imageBytes: idBackBytes, // attach to the same attempt
);

capturePassport(...) is a convenience wrapper over captureDocument(documentType: 'PASSPORT', documentSide: 'FRONT'). If you omit imageBytes, the SDK falls back to the native camera bridge.

6. Upload the selfie #

The selfie attaches to the attempt opened in step 5:

await sdk.captureSelfie(sessionId: session.sessionId, imageBytes: selfieBytes);

7. (Optional) Read the NFC chip #

Only when the tenant has NFC enabled and the document has a chip. The MRZ key (document number, DOB, expiry — all YYMMDD for the dates) comes from the printed machine‑readable zone:

try {
  await sdk.readAndSubmitNfc(
    sessionId:      session.sessionId,
    documentNumber: mrz.documentNumber,
    dateOfBirth:    mrz.dateOfBirth,   // YYMMDD
    expiryDate:     mrz.expiryDate,    // YYMMDD
    issuingCountry: 'USA',             // 3-letter ISO
  );
} on SullisNfcException {
  // NFC unavailable / read failed — continue with image-only verification.
}

8. Submit for verification #

submitVerification finalizes the attempt by calling the submit API, then prints the submit‑API response via debugPrint('[Sullis] Submit API response: …') and returns it immediately as a SullisVerificationResult:

final result = await sdk.submitVerification(
  sessionId: session.sessionId,
  timeout:   const Duration(seconds: 60),
);

switch (result.outcome) {
  case VerificationOutcome.verified:      onSuccess(result);                         break;
  case VerificationOutcome.declined:      onDeclined(result.failureReason);          break;
  case VerificationOutcome.manualReview:  onPending(result.manualReviewCaseId);      break;
  case VerificationOutcome.retryRequired: restartFromCapture();                      break;
  case VerificationOutcome.pending:       // still processing server-side — poll the
                                          // session later or wait for your webhook.
    break;
}

Behavior note: as of this version submitVerification returns as soon as the submit API responds and does not poll the session. If the submit response doesn't yet carry a terminal status, outcome is pending — use your webhook (or poll the session) for the authoritative final decision.


Complete example #

A full, runnable flow — document‑kind selection, passport vs. ID front/back, selfie, and submit — is in example/lib/screens/high_level_flow_screen.dart. Run the example app:

cd example
flutter run --dart-define=SULLIS_API_KEY=sk_test_… --dart-define=SULLIS_BASE_URL=http://10.0.2.2:8080

Capture modes & overlays #

CaptureMode selects the overlay and lens used by CaptureScreen:

CaptureMode Use for Lens
passportTd3 Passport photo page (TD‑3) Back
idCardTd1 National ID card (TD‑1) Back
drivingLicense Driving licence Back
cnicFront CNIC front Back
cnicBack CNIC back Back
face Selfie / liveness Front

Exported overlay widgets you can also use directly: PassportCameraOverlay, FaceCameraOverlay, NfcCapturePrompt.


Error handling #

Every error the SDK raises is a subclass of SullisException (a sealed type — exhaustive switch works):

Exception When it fires Suggested handling
SullisAuthException API key invalid / expired / revoked Re‑fetch key from your backend
SullisNetworkException No connectivity, timeout, DNS failure Offline UI; retry
SullisApiException Server returned 4xx/5xx (has .statusCode, .code) Surface a per‑code message
SullisCameraException Camera plugin failed Ask the user to retry
SullisPermissionException User denied camera/NFC Deep‑link to Settings
SullisNfcException NFC unavailable or read failed Fall back to image‑only
SullisSessionException Session/attempt missing, expired, or terminal Create a new session
SullisNotInitializedException API called before initialize() Call initialize() first
try {
  await sdk.submitVerification(sessionId: session.sessionId);
} on SullisApiException catch (e) {
  showError('${e.code} (${e.statusCode}): ${e.message}');
} on SullisException catch (e) {
  showError('${e.code}: ${e.message}');
}

Sandbox & testing #

Sandbox talks to a real backend with relaxed AI thresholds. Against a local docker‑compose stack:

await SullisKycSdk.initialize(
  baseUrl:     'http://10.0.2.2:8080',  // Android emulator → host machine
  apiKey:      'sk_test_<your-sandbox-key>',
  environment: SullisEnvironment.sandbox,
);

For unit tests, inject bytes so no camera is needed:

await sdk.captureDocument(
  sessionId:   session.sessionId,
  documentType:'PASSPORT',
  imageBytes:  testPassportJpegBytes, // skips the camera bridge
);

Production checklist #

Before flipping to SullisEnvironment.live:

  • ❌ Live API URL configured (HTTPS only)
  • ❌ API key fetched from your backend at runtime — not bundled
  • ❌ Webhook URL registered in tenant settings (authoritative for the final outcome)
  • ❌ Production NFC libraries wired in on both platforms
  • ❌ Camera + NFC permissions tested on real devices
  • ❌ Crash reporting capturing SullisException events
  • ❌ User‑facing error messages localized
  • ❌ Retry UX respecting the tenant maxRetryCount setting
  • ❌ App Store / Play Store privacy declarations cover biometric data

API surface #

Member Purpose
SullisKycSdk.initialize(...) One‑time setup (base URL, key, environment).
SullisKycSdk.instance The singleton, after init.
createSession(...) Start a customer session; refreshes tenant config.
captureDocument(...) / capturePassport(...) Upload a document side; manages attempts.
captureSelfie(...) Upload the selfie for the active attempt.
readAndSubmitNfc(...) Read & submit passport chip data (if enabled).
submitVerification(...) Submit the attempt; prints & returns the submit‑API response.
setCallbacks(...) Register stage callbacks.
dispose() Tear down; requires initialize() again afterwards.

Result/model types: SullisSession, DocumentResult, SelfieResult, SullisVerificationResult (outcome, riskScore, failureReason, manualReviewCaseId), VerificationOutcome, SdkConfig, SullisEnvironment, SullisCallbacks.

0
likes
120
points
70
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Liveness and KYC SDK for Flutter — passport and ID capture, MRZ, NFC chip reading, selfie liveness, face match, and backend verification.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

camera, crypto, flutter, http, image, path_provider

More

Packages that depend on mytm_liveness_sdk

Packages that implement mytm_liveness_sdk