Client SDKs

Myaza provides drop-in client SDKs that render the full verification UI — ID selection, document scan, and active liveness — and call the verification API for you. They are thin UI layers: they capture the user's data and media, upload it, and create the verification. The SDK uses a publishable (pk_) key — safe to ship in client code. Results arrive asynchronously on your backend via webhooks, or fetch the full result with a secret (sk_) key from your backend (the publishable key can only poll minimal status).

SDKPackagePlatform
Web@myazahq/kyc-sdk-reactReact (≥ 18)
Fluttermyaza_kyc_sdk_flutterFlutter / Dart

Both SDKs mirror each other feature-for-feature and call the same endpoints. The SDK resolves its base URL from the environment value — use a pk_test_ key with staging and a pk_live_ key with production.

Supported countries & ID types

Pass a subset of ID types via idTypes to limit what the user can pick; omit it to offer everything enabled for your organization in that country. Only types valid for the selected country are accepted.

CountryReact idTypes keysFlutter IdType values
NG Nigeriabvn, nin, vnin, passport, drivers-license, pvcbvn, nin, vnin, passport, driversLicense, pvc
GH Ghanaghana-card, voters, drivers-license, ssnit, passportghanaCard, voters, driversLicense, ssnit, passport
KE Kenyanational-id, passportnationalId, passport
ZA South Africanational-idnationalId
CI Côte d'Ivoirecni, residence-cardcni, residenceCard

See ID types for the document requirements (capture vs. number-only, scan sides) of each.


Web SDK (React)

Install

shell
pnpm add @myazahq/kyc-sdk-react
shell
yarn add @myazahq/kyc-sdk-react
shell
npm install @myazahq/kyc-sdk-react

Usage

<MyazaKYC /> renders a "Verify Identity" button plus the full modal flow. Import the bundled stylesheet once, anywhere in your app.

tsx
"use client";

import { MyazaKYC } from "@myazahq/kyc-sdk-react";
import "@myazahq/kyc-sdk-react/styles.css";

export default function VerifyButton() {
  return (
    <MyazaKYC
      apiKey="pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      environment="staging"
      country="NG"
      idTypes={["bvn", "drivers-license", "passport"]}
      userData={{ firstName: "Jane", lastName: "Doe" }}
      enableSelfie
      enableDocumentCapture
      enableLiveness
      showThemeToggle
      appearance={{ primaryColor: "#5645F5", companyName: "Myaza", logo: "default", theme: "light" }}
      consent={{ title: "Welcome, {firstName}", description: "A quick check to confirm it's really you." }}
      success={{ title: "You're all set, {firstName}!", description: "We'll email you once your verification is reviewed." }}
      metadata={{ requestId: "order_1001", userId: "user_42" }}
      onSubmit={(submission) => {
        // The verification was created; status is always 'pending'.
        // Reconcile the final result on your backend via webhook or a secret-key
        // GET /verifications/:id call (never from the client).
        console.log("submitted", submission.verificationId);
      }}
      onError={(err) => console.error(err)}
      onClose={() => console.log("closed")}
    />
  );
}

Props

PropTypeDefaultDescription
apiKeystringRequired. Sent as Authorization: Bearer. pk_test_* runs in staging, pk_live_* in production.
environment'staging' | 'production'Required. Selects the API base URL — pk_test_* with staging, pk_live_* with production.
country'NG' | 'GH' | 'KE' | 'ZA' | 'CI'Required. Country whose ID types are offered.
idTypesIdType[]all enabled for orgSubset of ID types to offer; must be valid for country.
userData{ firstName?, lastName?, dateOfBirth? }Pre-fills the user's details; fields provided here aren't asked again.
enableSelfiebooleantrueCapture a selfie during liveness.
enableDocumentCapturebooleantrueEnable the document-scan step for document IDs.
enableLivenessbooleantrueRun the liveness challenge step. The server can still disable it per ID type.
showThemeTogglebooleanfalseShow a light/dark toggle inside the modal header.
appearanceKYCAppearancebrand defaultsBrand & theme the modal — colors, logo, light/dark. See Branding & theming.
consentKYCConsentContentbuilt-in copyOverride the consent/welcome screen title and description. See Consent screen copy.
successKYCSuccessContentbuilt-in copyOverride the success/submitted screen title and description. See Success screen copy.
metadataRecord<string, string>Forwarded with the verify request (include your requestId).
onStart() => voidCalled when the flow opens.
onStepChange(step: KYCStep) => voidCalled on each step transition.
onSubmit(submission: KYCSubmission) => voidCalled when the server accepts the verification. status is always 'pending'.
onClose() => voidCalled when the user closes the flow.
onError(error: Error) => voidCalled for technical errors only (network, 401, 402, upload). Verification outcomes never come through here.

Programmatic control

For a custom trigger instead of the built-in button, wrap your tree in KYCProvider and drive the flow with the useMyazaKYC() hook:

tsx
import { KYCProvider, useMyazaKYC } from "@myazahq/kyc-sdk-react";

function Trigger() {
  const { open, close, isOpen, currentStep } = useMyazaKYC({
    apiKey: "pk_test_…",
    environment: "staging",
    country: "NG",
    onSubmit: (s) => console.log(s.verificationId),
  });

  return <button onClick={open} disabled={isOpen}>Verify ({currentStep ?? "idle"})</button>;
}

export default () => (
  <KYCProvider>
    <Trigger />
  </KYCProvider>
);

The flow advances through these KYCStep values, reported via onStepChange / currentStep: consentid-typeid-inputdocument-capturelivenesssubmitted.


Flutter SDK

Install

Add the dependency to your pubspec.yaml, then run flutter pub get:

yaml
dependencies:
  myaza_kyc_sdk_flutter: ^1.0.0

Usage

MyazaKYC.show() opens the full flow as a modal bottom sheet (a full-screen page on Android). Note that context is a named parameter, and the callbacks are passed to show() alongside config — not inside it.

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

void startKYC(BuildContext context) {
  MyazaKYC.show(
    context: context,
    config: MyazaKYCConfig(
      apiKey: 'pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
      environment: KYCEnvironment.staging,
      country: Country.NG,
      idTypes: const [IdType.bvn, IdType.driversLicense, IdType.passport],
      userData: const UserData(firstName: 'Jane', lastName: 'Doe'),
      enableSelfie: true,
      enableDocumentCapture: true,
      enableLiveness: true,
      appearance: const MyazaKYCAppearance(
        primaryColor: Color(0xFF5645F5),
        companyName: 'Myaza',
        logo: 'default',
        theme: MyazaThemeMode.light,
      ),
      consent: const KYCConsentContent(
        title: 'Welcome, {firstName}',
        description: "A quick check to confirm it's really you.",
      ),
      success: const KYCSuccessContent(
        title: "You're all set, {firstName}!",
        description: "We'll email you once your verification is reviewed.",
      ),
      metadata: const {'requestId': 'order_1001', 'userId': 'user_42'},
    ),
    onSubmit: (submission) {
      // The verification was created; submission.status is always 'pending'.
      // The final result arrives via webhook to your backend (or fetch it with a
      // secret-key GET /verifications/:id call — never from the client).
      debugPrint('Submitted: ${submission.verificationId}');
    },
    onError: (error) {
      // Technical errors only (network / 401 / 402 / upload).
      debugPrint('Error: ${error.code} — ${error.message}');
    },
    onClose: () => debugPrint('KYC closed'),
  );
}

Config (MyazaKYCConfig)

FieldTypeDefaultDescription
apiKeyStringRequired. Sent as Authorization: Bearer. pk_test_* = staging, pk_live_* = production.
countryCountryRequired. Country whose ID types are offered.
environmentKYCEnvironmentproductionBackend the SDK talks to. Base URL is resolved on mount.
idTypesList<IdType>?all for countrySubset of ID types to offer; null shows all for country.
userDataUserData?Pre-fills the user's details.
enableSelfiebooltrueCapture a selfie during liveness.
enableDocumentCapturebooltrueEnable the document-scan step for document IDs.
enableLivenessbooltrueRun the liveness challenge step. The server can still disable it per ID type.
appearanceMyazaKYCAppearance?brand defaultsBrand & theme the flow — colors, logo, light/dark. See Branding & theming.
consentKYCConsentContent?built-in copyOverride the consent/welcome screen title and description. See Consent screen copy.
successKYCSuccessContent?built-in copyOverride the success/submitted screen title and description. See Success screen copy.
metadataMap<String, dynamic>?Forwarded with the verify request (include your requestId).
livenessConfigLivenessConfig?2 challenges, 8s eachTune the liveness challenge sequence (see below).

UserData accepts firstName, lastName, dateOfBirth, gender, address, and phoneNumber (all optional).

Callbacks

Passed to MyazaKYC.show() alongside config:

CallbackTypeDescription
onSubmitvoid Function(KYCSubmission)Called when the server accepts the verification. status is always 'pending'.
onErrorvoid Function(KYCError)Called for technical errors only (network, 401, 402, upload). Verification outcomes don't come through here.
onClosevoid Function()Called when the user closes the flow.

Enums

EnumValues
CountryNG, GH, KE, ZA, CI
IdTypebvn, nin, vnin, passport, driversLicense, pvc, ghanaCard, voters, ssnit, nationalId, cni, residenceCard
KYCEnvironmentstaging, production
MyazaThemeModelight, dark

Liveness configuration

LivenessConfig tunes the active-liveness step. Defaults match the web SDK.

FieldTypeDefaultDescription
challengeCountint2Number of gesture challenges drawn from the pool.
challengePoolList<ChallengeConfig>?kDefaultChallengePoolThe set of challenges to draw from.
timeoutPerChallengeint8Seconds allowed per challenge before it fails.
enableAvatarbooltrueShow the animated avatar that demonstrates each gesture.

The default pool covers four LivenessChallenge gestures: nod, turn, blink, and smile.


Branding & theming

Both SDKs accept an appearance object to match the flow to your brand. The same fields exist on each platform — React takes CSS color strings (e.g. "#5645F5"); Flutter takes Color values. On the web, the colors are injected as CSS variables scoped to the SDK (they never leak into your page); setting one color cascades to all of its shades.

FieldReact typeFlutter typeDescription
primaryColorstringColor?Brand color — buttons, selected states, focus rings, the shield hero.
primaryTextColorstringColor?Text/icons rendered on top of primaryColor (e.g. button labels).
accentColorstringColor?Subtle hover/active surfaces.
backgroundColorstringColor?Modal/sheet background.
surfaceColorstringColor?Cards & panels.
borderColorstringColor?Borders and input outlines.
textColorstringColor?Primary text color.
companyNamestringStringShown on the trigger button and the persistent header.
logostringString?Image URL, or 'default' for your dashboard logo (see below).
theme'light' | 'dark'MyazaThemeModeInitial light/dark mode. With the web showThemeToggle, users can flip it.

Flutter also accepts logoAsset (a bundled Image.asset path) as a fallback when logo is not set.

The logo renders as a small circular avatar in the header (top-left), persistent on every step, next to companyName.

  • An image URL ("https://…/logo.png") is used directly.
  • 'default' uses the logo configured for your organization under Settings → Organization in the dashboard. The SDK fetches it from GET /config (branding.logo) on mount. If your org has no logo set — or the image fails to load — it falls back to the built-in shield icon.
  • Omitted shows no header logo.
tsx
// React — brand with your dashboard logo and a custom palette
appearance={{
  primaryColor: "#0F7B6C",
  primaryTextColor: "#FFFFFF",
  surfaceColor: "#F4F7F6",
  logo: "default",
  theme: "light",
}}

The first screen of the flow (the welcome/consent step) shows a heading and a short description. Both default to Myaza's built-in copy, but you can override either through the consent object — identical fields on both SDKs.

FieldReact typeFlutter typeDescription
titlestringString?Heading. Defaults to Welcome, {firstName} when a first name is known, otherwise Identity Verification.
descriptionstringString?Sub-text under the heading. Defaults to the built-in regulatory copy.

Both fields support {firstName} and {lastName} tokens, which are replaced with the values you pass in userData (an empty string when absent) — so a custom title can still greet the user by name.

tsx
// React
consent={{
  title: "Welcome, {firstName}",
  description: "We just need to confirm it's really you. This takes about a minute.",
}}
dart
// Flutter
consent: const KYCConsentContent(
  title: 'Welcome, {firstName}',
  description: "We just need to confirm it's really you. This takes about a minute.",
),

Omit consent (or either field) to keep the defaults.

Success screen copy

The final screen — shown after the user submits — displays a confirmation heading and a short description. Both default to Myaza's built-in copy, but you can override either through the success object — identical fields on both SDKs.

FieldReact typeFlutter typeDescription
titlestringString?Heading. Defaults to Verification Submitted!.
descriptionstringString?Sub-text under the heading. Defaults to the built-in "submitted for review" copy.

Both fields support the same {firstName} / {lastName} tokens as consent, which are replaced with the values you pass in userData (an empty string when absent).

tsx
// React
success={{
  title: "You're all set, {firstName}!",
  description: "We'll email you once your verification is reviewed.",
}}
dart
// Flutter
success: const KYCSuccessContent(
  title: "You're all set, {firstName}!",
  description: "We'll email you once your verification is reviewed.",
),

Omit success (or either field) to keep the defaults. This screen is purely cosmetic — the verification outcome still arrives asynchronously (see Handling results).

Handling results

The SDK finishing only means the verification was created — never that it passed. Treat the outcome as eventual:

  1. The SDK uploads media and creates the verification, then invokes onSubmit with a KYCSubmission.
  2. Your backend receives the terminal verification.* webhook (or fetches the full result with a secret key; the SDK's publishable key can only poll minimal status).
  3. You update the user's state from that authoritative result.

The onSubmit payload (KYCSubmission) carries:

json
{
  "verificationId": "ver_…",   // use this to correlate the webhook / status poll
  "status": "pending",          // always 'pending' at this point
  "metadata": { "requestId": "order_1001" },
  "submittedAt": "2026-04-27T12:00:00.000Z"
}

Always pass a stable requestId in metadata so retries are idempotent.

Errors

onError fires for technical failures only — never for a failed verification, which arrives asynchronously via webhook. The error carries a stable code:

CodeMeaning
network_errorConnection failure or timeout.
invalid_api_keyServer returned 401 — check the key and that it matches environment.
insufficient_creditsServer returned 402. details includes { required, balance, currency }.
upload_failedMedia upload failed.
unknownAnything else.

In React, onError receives a standard Error; in Flutter it receives a KYCError with code, message, and optional details. See Errors for the API-level error model.

For the full prop reference and styling options, see each package's README.