Client SDKs
Build with

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)
React Native@myazahq/kyc-sdk-react-nativeReact Native / Expo (SDK ≥ 56)
Fluttermyaza_kyc_sdk_flutterFlutter / Dart

All three SDKs share the same core flow and call the same endpoints. The SDK detects the environment automatically from the API key prefix and resolves the base URL itself — pk_test_* → sandbox, pk_live_* → production. There is no environment option. Some newer capabilities are web SDK only for now — see Feature availability.

Platform guides
Build with

Each SDK has its own integration guide — pick your platform:

  • Web SDK (React)<MyazaKYC /> button + modal flow for React (≥ 18).
  • React Native SDK — on-device native liveness; needs a custom dev build (not Expo Go).
  • Flutter SDKMyazaKYC.show() modal sheet for Flutter / Dart.

The rest of this page covers what's shared across all three — supported countries, branding, consent/success copy, handling results, errors, and resilience.

The React Native SDK ships native code (on-device liveness via Apple Vision + Google ML Kit), so it needs a custom dev/native build — it does not run in Expo Go. See the React Native SDK guide.

Feature availability
Build with

The core flow — document capture, selfie, active liveness, consent/success copy, branding, and the callbacks — is identical across all three SDKs. Some newer capabilities have landed on the web SDK first while the native SDKs catch up:

CapabilityWeb (2.4.x)React Native (2.0.x)Flutter (2.0.x)
Core flow (docs · selfie · liveness · branding · copy)
WorkflowsworkflowId embed
Capture add-ons — contact OTP, proof of address, questionnaire
Business (KYB) verification
Global & multi-region countries

Until the native SDKs reach parity, drive those flows on iOS/Android from the web SDK (hosted link or embed) — or configure them in a workflow and mount the web SDK. The NFC chip read is an on-device capability, so it will land on the native SDKs as they reach parity; the web SDK only renders the chip screen for the dashboard builder preview.

Supported countries & ID types
Build with

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.

React and React Native use the same kebab-case idTypes keys; Flutter uses IdType enum values.

CountryReact & React Native 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.

The five countries above are the government-database-backed markets, available on all three SDKs. The web SDK additionally verifies any ISO country via document capture (Global Documents) — pass any two-letter country code (your organization's grants are enforced server-side). The React Native and Flutter SDKs are limited to the five above for now.


Branding & theming
Build with

All three SDKs accept an appearance object to match the flow to your brand. The same fields exist on each platform — React and React Native take CSS-style 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. React Native uses the identical field names and string values.

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. When showThemeToggle is enabled, users can flip it; otherwise the flow stays on this value.

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
Build with

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
Build with

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
Build with

onError fires for technical failures only — never for a failed verification, which arrives asynchronously via webhook. The error carries a stable code (the same set across all three SDKs):

CodeMeaning
network_errorConnection failure or timeout, after automatic retries are exhausted.
invalid_api_keyServer returned 401 — check the key (its prefix selects the environment automatically).
insufficient_creditsServer returned 402. details includes { required, balance, currency }.
upload_failedMedia upload failed, after automatic retries are exhausted.
camera_permission_deniedThe user denied (or the OS/browser blocks) camera access.
feature_disabledServer returned 403 — the ID type or a verification feature isn't enabled for your organization.
unknownAnything else.

All SDKs deliver the same typed error: in React and React Native onError receives a KYCError — a real Error (so existing (error: Error) handlers keep working) that also carries code and optional details; in Flutter it receives a KYCError with code, message, and optional details. See Errors for the API-level error model.

Voice guidance is text-to-speech output — it never records audio, so there is no microphone permission and no microphone error code.

Resilience & capture quality
Build with

All three SDKs handle real-world conditions so the flow doesn't crash, hang, or capture unusable media:

  • Network retries. Media uploads and the verify submission retry transient failures (network / timeout / 5xx) with exponential backoff + jitter; terminal 4xx surface immediately. The UI shows "Reconnecting… / retrying (n/3)…", and onError fires only after retries are exhausted.

  • Camera permission. A denied camera shows a clear "camera access needed" screen (with how to re-enable it — an Open Settings action on Flutter & React Native) and reports camera_permission_denied. Document capture keeps a gallery-upload fallback on that screen as an escape hatch.

  • Multiple faces. During liveness, if more than one face is in frame the challenge pauses ("Make sure only your face is visible") and resumes automatically when only one remains — guarding capture quality and a class of spoofing.

  • Lighting. Too-dark and too-bright (glare) conditions are detected live and surfaced as guidance ("Move to a brighter area" / "Too bright — reduce glare"); auto-capture is discouraged until lighting is acceptable.

  • Liveness is on-device & native on mobile — Apple Vision (iOS) + Google ML Kit (Android) on both React Native and Flutter; the web SDK uses MediaPipe in the browser. The selfie is auto-captured once the challenges pass (you can't trigger it on a static image), and a short liveness video is recorded for server-side review.

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