Skip to main content

apenia_verify 0.0.1 copy "apenia_verify: ^0.0.1" to clipboard
apenia_verify: ^0.0.1 copied to clipboard

Apenia Verify — KYC capture SDK. Native active-liveness selfie + document scan; matching runs server-side in Apenia Verify.

apenia_verify #

Flutter KYC capture SDK for Apenia Verify.

The handset captures — native active-liveness selfie (ML Kit on Android, Vision on iOS) and document scan (CameraX / AVFoundation). The matching engine lives server-side in Apenia Verify: face-match, liveness/PAD scoring and any ID-authority lookup all happen there. The device never decides pass/fail and never holds the secret key.

This mirrors the ergonomics of the smile_id package (drop-in product screens, PartnerParams, create-session → upload → callback) but keeps the verification intelligence on your own infrastructure.

Capture UX #

  • Document — fixed enclosure, no enhancement. A contained (non-fullscreen) camera with a document-shaped frame sized to the physical doc (ID-1 card 1.586, passport data page 1.42). Built on plain CameraX / AVFoundation — not the OS document scanner — so there are no enhancement, filter or re-crop options. The saved image is cropped to exactly the frame. Front, then back for two-sided documents.
  • Confirm / retake. After each side: "Is the document clear and readable?" → Yes, use this / Retake photo.
  • Auto or manual capture. ApeniaConfig.documentCaptureMode selects manual (shutter, default) or auto (fires once the document is detected in frame). The shutter always works as a manual override in auto mode.
  • On-device detail extraction. After the MRZ-bearing side (ID back / passport page) the SDK OCRs it on-device — ML Kit on Android, Vision on iOS — and parses the MRZ (TD1 / TD3) into full name, ID number, date of birth, sex, nationality and expiry. Kenyan IDs yield both the serial number and the national ID number, in that order.
  • Face — controlled face-scanner. A dimmed oval overlay guides the user; capture won't start until the face is centred, correctly sized, frontal and eyes-open (the ring turns green). Then the active-liveness challenges run.
  • 100% live data — no static path. The selfie primary and every liveness frame are sampled only during the live session, and the capture fails unless the full challenge sequence completes with a face present throughout. If the face is lost mid-sequence, positioning restarts. There is no code path that returns a still or gallery image, so what's uploaded is always live capture.

Liveness honesty: the free on-device path is active liveness — it proves a live person is present and following prompts (blink / turn / smile). It is not ISO/IEC 30107-3 presentation-attack detection. Apenia Verify runs passive PAD scoring server-side on the uploaded frames. If you later need certified on-device passive PAD, swap the native capture module for a licensed vendor SDK (FaceTec / iProov / Regula) behind the same ApeniaVerifyPlatform interface — the Dart API and upload flow don't change.

Install #

dependencies:
  apenia_verify:
    path: ../apenia_verify   # or a git/hosted ref

Android #

  • minSdk ≥ 24. The CAMERA permission and capture activities are declared in the plugin manifest and merged automatically.

iOS #

  • Deployment target ≥ 13.0.
  • Add to the host app's Info.plist:
    <key>NSCameraUsageDescription</key>
    <string>Used to capture your selfie and ID document for verification.</string>
    

Usage #

await ApeniaVerify.initialize(ApeniaConfig(
  baseUrl: 'https://verify.apenia.com',
  sessionProvider: (req) async {        // your backend creates the session with sk_…
    final json = await myBackend.createSession(flow: req.flow, metadata: req.params.toJson());
    return ApeniaSession.fromJson(json); // { session_id, upload_url, client_token }
  },
));

// Drop-in product screen:
ApeniaBiometricKYC(
  params: PartnerParams(userId: user.id, jobId: uuid(), jobType: 'biometric_kyc'),
  idType: 'NATIONAL_ID',
  idNumber: '12345678',
  onSuccess: (VerifyVerdict v) => print(v.status),
  onError: (e) => print(e),
);

Or call the facade directly without the bundled UI:

final verdict = await ApeniaVerify.instance.runDocumentVerification(
  params: PartnerParams(userId: user.id, jobId: uuid()),
  captureBack: true,
);

Architecture #

Dart app ─▶ ApeniaVerify (facade)
              ├─ native capture ──MethodChannel──▶ Android: LivenessActivity / DocumentCaptureActivity (ML Kit + CameraX)
              │                                    iOS:     LivenessViewController / DocumentCaptureViewController (Vision + AVFoundation)
              └─ ApeniaClient  ──HTTPS──▶ Apenia Verify API (create session → pre-signed PUT → poll/IPN)
                                            └─ face-match + PAD scoring + ID-authority lookup

Capture returns local file paths over the channel (small payload); the Dart ApeniaClient zips the frames + an info.json manifest and uploads.

Server contract (SDK capture flow) #

Full backend spec to hand to the API team: doc/onboarding-sessions-api.md.

This is the SDK sibling of Apenia's hosted onboarding-links product. The hosted flow (POST /v1/onboarding/links → hosted URL) runs capture on Apenia's page; the SDK flow runs capture on-device and submits against an onboarding session. One credential per layer: your backend creates the session with the secret sk_…; the app authenticates its calls with only the short-lived Bearer client token that create-session returned.

The public Apenia docs currently document only the hosted links flow. The session endpoints below are the natural SDK counterpart — confirm the exact paths with the Apenia backend; if they differ, only _sessionsPath in apenia_client.dart changes. The result schema is already the real one.

1. POST /v1/onboarding/sessions (backend-proxied; mints the session) #

// request
{ "flow": "biometric_kyc", "metadata": { "user_id": "…", "job_id": "…", "customer_ref": "…" } }
// response
{ "session_id": "onb_…", "upload_url": "https://s3…/presigned-put" }

2. PUT <upload_url> (Content-Type: application/zip) #

A zip containing info.json (the manifest) plus the JPEG frames: selfie.jpg, liveness_0.jpg…, id_front.jpg, id_back.jpg.

3. GET /v1/onboarding/sessions/{session_id} (or the onboarding.completed IPN) #

{ "session_id": "onb_…",
  "status": "completed | pending | processing | failed | review",
  "subject_id": "sub_…",
  "result": {
    "face_match": { "score": 0.95, "match": true },
    "liveness":   { "score": 0.98, "live": true },
    "voice":      { "score": 0.90 },
    "identity_confidence": 84
  } }

Prefer the IPN to your backend as the source of truth; the SDK polls this endpoint only to give the caller a synchronous VerifyVerdict.

Flows #

biometric_kyc, doc_verification (extendable: authentication, enhanced_document_verification).

Security & compliance notes #

  • No secret key on device. sessionProvider returns the session your backend created with the secret sk_…; the app only ever holds the short-lived client_token.
  • Idempotent job_id. A retried upload with the same job_id must not create a duplicate KYC record server-side.
  • Consent + audit. Capture explicit user consent before launching a product screen and log it (Kenya DPA / GDPR). The raw map on VerifyVerdict is retained for your audit trail.
  • Data minimisation. Frames live in the OS cache/temp dir only until upload; delete them after a successful submit in production.

Status #

Scaffolded reference implementation. The Dart layer analyzes clean; the native capture flows are structurally complete but need on-device testing and UX polish (framing overlays, retry limits, timeout handling) before production.

0
likes
0
points
236
downloads

Publisher

unverified uploader

Weekly Downloads

Apenia Verify — KYC capture SDK. Native active-liveness selfie + document scan; matching runs server-side in Apenia Verify.

Homepage

License

unknown (license)

Dependencies

archive, flutter, http, plugin_platform_interface

More

Packages that depend on apenia_verify

Packages that implement apenia_verify