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).
| SDK | Package | Platform |
|---|---|---|
| Web | @myazahq/kyc-sdk-react | React (≥ 18) |
| React Native | @myazahq/kyc-sdk-react-native | React Native / Expo (SDK ≥ 56) |
| Flutter | myaza_kyc_sdk_flutter | Flutter / 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
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 SDK —
MyazaKYC.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
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:
| Capability | Web (2.4.x) | React Native (2.0.x) | Flutter (2.0.x) |
|---|---|---|---|
| Core flow (docs · selfie · liveness · branding · copy) | ✅ | ✅ | ✅ |
Workflows — workflowId 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
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.
| Country | React & React Native idTypes keys | Flutter IdType values |
|---|---|---|
NG Nigeria | bvn, nin, vnin, passport, drivers-license, pvc | bvn, nin, vnin, passport, driversLicense, pvc |
GH Ghana | ghana-card, voters, drivers-license, ssnit, passport | ghanaCard, voters, driversLicense, ssnit, passport |
KE Kenya | national-id, passport | nationalId, passport |
ZA South Africa | national-id | nationalId |
CI Côte d'Ivoire | cni, residence-card | cni, 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
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.
| Field | React type | Flutter type | Description |
|---|---|---|---|
primaryColor | string | Color? | Brand color — buttons, selected states, focus rings, the shield hero. |
primaryTextColor | string | Color? | Text/icons rendered on top of primaryColor (e.g. button labels). |
accentColor | string | Color? | Subtle hover/active surfaces. |
backgroundColor | string | Color? | Modal/sheet background. |
surfaceColor | string | Color? | Cards & panels. |
borderColor | string | Color? | Borders and input outlines. |
textColor | string | Color? | Primary text color. |
companyName | string | String | Shown on the trigger button and the persistent header. |
logo | string | String? | Image URL, or 'default' for your dashboard logo (see below). |
theme | 'light' | 'dark' | MyazaThemeMode | Initial 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.
Logo
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 fromGET /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.
// React — brand with your dashboard logo and a custom palette
appearance={{
primaryColor: "#0F7B6C",
primaryTextColor: "#FFFFFF",
surfaceColor: "#F4F7F6",
logo: "default",
theme: "light",
}}Consent screen copy
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.
| Field | React type | Flutter type | Description |
|---|---|---|---|
title | string | String? | Heading. Defaults to Welcome, {firstName} when a first name is known, otherwise Identity Verification. |
description | string | String? | 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.
// React
consent={{
title: "Welcome, {firstName}",
description: "We just need to confirm it's really you. This takes about a minute.",
}}// 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.
| Field | React type | Flutter type | Description |
|---|---|---|---|
title | string | String? | Heading. Defaults to Verification Submitted!. |
description | string | String? | 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).
// React
success={{
title: "You're all set, {firstName}!",
description: "We'll email you once your verification is reviewed.",
}}// 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:
- The SDK uploads media and creates the verification, then invokes
onSubmitwith aKYCSubmission. - 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). - You update the user's state from that authoritative result.
The onSubmit payload (KYCSubmission) carries:
{
"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
requestIdinmetadataso 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 (the same set across all three SDKs):
| Code | Meaning |
|---|---|
network_error | Connection failure or timeout, after automatic retries are exhausted. |
invalid_api_key | Server returned 401 — check the key (its prefix selects the environment automatically). |
insufficient_credits | Server returned 402. details includes { required, balance, currency }. |
upload_failed | Media upload failed, after automatic retries are exhausted. |
camera_permission_denied | The user denied (or the OS/browser blocks) camera access. |
feature_disabled | Server returned 403 — the ID type or a verification feature isn't enabled for your organization. |
unknown | Anything 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
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; terminal4xxsurface immediately. The UI shows "Reconnecting… / retrying (n/3)…", andonErrorfires 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.