@myazahq/kyc-sdk-react
v2.1.1
Published
React component library for Myaza KYC — drop-in identity verification (ID capture, document scan, and active liveness) that talks to the Myaza KYC API.
Downloads
598
Readme
@myazahq/kyc-sdk-react
React component library for Myaza KYC — drop-in identity verification (ID capture, document scan, and active liveness) that talks to the Myaza KYC API.
Installation
pnpm add @myazahq/kyc-sdk-reactyarn add @myazahq/kyc-sdk-reactnpm install @myazahq/kyc-sdk-reactUsage
<MyazaKYC /> renders a "Verify Identity" button plus the full modal flow. The trigger is a real <button> — pass children to relabel it, className to restyle it, or any other button attribute (disabled, type, aria-*, …). See Trigger button. Import the bundled stylesheet once, anywhere in your app.
"use client";
import { MyazaKYC } from "@myazahq/kyc-sdk-react";
import "@myazahq/kyc-sdk-react/styles.css";
export default function VerifyButton() {
return (
<MyazaKYC
apiKey='pk_live_xxx'
country='NG'
idTypes={["passport", "drivers-license", "bvn", "nin", "pvc"]}
userData={{ firstName: "Jane", lastName: "Doe" }}
enableSelfie={true}
enableDocumentCapture={true}
enableLiveness={true}
showThemeToggle={true}
appearance={{
primaryColor: "#5645F5",
companyName: "Myaza",
logo: "default",
theme: "dark",
}}
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={{ userId: "test_user_123" }}
onStart={() => console.log("KYC started")}
onStepChange={(step) => console.log("Step:", step)}
onSubmit={(submission) => {
// Fires as soon as the server accepts the request.
// submission.status is always 'pending' — the result arrives later via
// webhook to your backend (or poll GET /api/kyc/status/:id).
console.log("Submitted!", submission.verificationId);
}}
onClose={() => console.log("Modal closed")}
onError={(err) => console.error("SDK error:", err)}
/>
);
}Props
| Prop | Type | Default | Description |
| ----------------------- | ----------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------- |
| apiKey | string | — | Required. Sent as Authorization: Bearer. The environment is derived from the key prefix (pk_test_… → sandbox, pk_live_… → production); an unrecognized prefix throws. |
| country | 'NG' \| 'GH' \| 'KE' \| 'ZA' \| 'CI' | — | Required. Country whose ID types are offered. |
| idTypes | IdType[] | all allowed for org | Subset of ID types to offer; must be valid for country. |
| userData | { firstName?, lastName?, dateOfBirth? } | — | Pre-fills the user's details. |
| enableSelfie | boolean | true | Capture a selfie during liveness. |
| enableDocumentCapture | boolean | true | Enable the document-scan step for document IDs. |
| allowDocumentUpload | boolean | true | Allow picking a document photo from the device (gallery / drag-and-drop) as an alternative to the camera. false hides every "upload instead" affordance (it's still offered on the camera-permission-denied screen as an escape hatch). |
| enableLiveness | boolean | true | Run the liveness challenge step. The server can still disable it per ID type. |
| voiceGuidance | boolean \| { enabled?, language? } | true | Spoken liveness instructions (accessibility, TTS output — no microphone). false mutes it; pass { language: 'fr-FR' } to set the voice. See Robustness & error handling. |
| showThemeToggle | boolean | false | Show a light/dark toggle inside the modal. |
| deviceHandoff | boolean | true | On desktop, show a "continue on your phone" screen (QR + copyable link) before the flow starts — handy when the computer has no webcam. The user can still choose to continue on the current device, and when they finish on their phone the desktop completes automatically. Set false to disable. Has no effect on mobile/touch devices. |
| appearance | KYCAppearance | brand defaults | Brand & theme the modal — colors, logo, light/dark. See Appearance & theming. |
| consent | KYCConsentContent | built-in copy | Override the consent/welcome screen title and description. See Consent screen copy. |
| success | KYCSuccessContent | built-in copy | Override the success/submitted screen title and description. See Success screen copy. |
| metadata | Record<string, string> | — | Forwarded with every verify request. |
| onStart | () => void | — | Called when the flow opens. |
| onStepChange | (step: KYCStep) => void | — | Called on each step transition. |
| onSubmit | (submission: KYCSubmission) => void | — | Called when the server accepts the verification. status is always 'pending'. |
| onError | (error: KYCError) => void | — | Called for technical errors only. Receives a typed KYCError (a real Error with a code). Verification outcomes never come through here. |
| onClose | () => void | — | Called when the user closes the flow. |
| children | ReactNode | Verify Identity | Trigger button label/content. Defaults to Verify with {companyName} when companyName is set, else Verify Identity. |
| className | string | — | Trigger button classes. Merged via tailwind-merge, so your classes override the built-in styling. |
| other button attrs | ButtonHTMLAttributes | — | Any standard <button> attribute (disabled, type, aria-*, style, …) is forwarded. onClick is reserved by the SDK. |
Environment
There is no environment prop — the SDK derives the environment (and the
base URL) from the API key prefix, which is the single source of truth:
| Key prefix | Environment | Base URL |
|---|---|---|
| pk_test_… / sk_test_… | sandbox | https://identity.myaza.app |
| pk_live_… / sk_live_… | production | https://identity.myaza.app |
An unrecognized or malformed key throws at setup (it never silently defaults).
Trigger button
<MyazaKYC /> renders a real <button>. Beyond the config props above it accepts standard button attributes (the props type is exported as MyazaKYCProps), so you can treat it like any other button:
<MyazaKYC
{...config}
className="w-full rounded-full bg-black px-6 text-white"
disabled={!ready}
>
Start verification
</MyazaKYC>childrensets the label (falls back toVerify with {companyName}/Verify Identity).classNameis merged throughtailwind-merge, so your classes win over the defaults.styleis merged on top of the SDK's injected theme variables, so theming still applies.onClickis owned by the SDK (it opens the modal) and can't be overridden. For a fully custom trigger element, use theuseMyazaKYC()hook and wire itsopen()to your own component.
Appearance & theming
Pass an appearance object to brand the flow. Colors are injected as CSS variables scoped to the SDK — they never leak into your page's styles. Because the UI is token-driven, setting one color cascades to all of its shades (hover/selected/focus states included).
| Field | Type | Description |
| ------------------ | ------------------- | -------------------------------------------------------------------------------- |
| primaryColor | string | Brand color — buttons, selected states, focus rings. Defaults to #5645F5. |
| primaryTextColor | string | Text/icons rendered on top of primaryColor (e.g. button labels). |
| accentColor | string | Subtle hover/active surfaces. |
| backgroundColor | string | Modal background. |
| surfaceColor | string | Cards & panels. |
| borderColor | string | Borders and input outlines. |
| textColor | string | Primary text color. |
| companyName | string | Used on the verify button (“Verify with …”) and the persistent header. |
| logo | string | Image URL, or 'default' to use your org's logo. See below. |
| theme | 'light' \| 'dark' | Initial mode (defaults to 'light'). With showThemeToggle, users can flip it. |
Logo
The org logo renders as a small circular avatar in the modal header (top-left), persistent on every step, alongside companyName.
logo: 'https://…/logo.png'— uses that image directly.logo: 'default'— pulls your organization's logo configured in the Myaza dashboard (returned by the server on mount). If your org has no logo set, or the image fails to load, it falls back to a built-in shield icon.- omitted — no header logo.
appearance={{
primaryColor: "#0F7B6C",
primaryTextColor: "#FFFFFF",
surfaceColor: "#F4F7F6",
borderColor: "#D7E3E0",
logo: "default",
theme: "light",
}}Consent screen copy
The welcome/consent step shows a heading and a short description. Override either through the consent prop:
| Field | Type | Description |
| ------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| title | string | Heading. Defaults to Welcome, {firstName} when a first name is known, else Identity Verification. |
| description | string | Sub-text under the heading. Defaults to the built-in regulatory copy. |
Both fields support {firstName} and {lastName} tokens, replaced with the values from userData (empty string when absent), so a custom title can still greet the user by name.
consent={{
title: "Welcome, {firstName}",
description: "We just need to confirm it's really you. This takes about a minute.",
}}Success screen copy
After the user submits, the final screen shows a confirmation heading and description. Override either through the success prop:
| Field | Type | Description |
| ------------- | -------- | -------------------------------------------------------------------------- |
| title | string | Heading. Defaults to Verification Submitted!. |
| description | 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, replaced with the values from userData (empty string when absent).
success={{
title: "You're all set, {firstName}!",
description: "We'll email you once your verification is reviewed.",
}}Robustness & error handling
The SDK is resilient to flaky networks, denied permissions, and poor capture
conditions, and reports technical failures through onError with a typed code.
Typed errors (onError)
onError receives a KYCError — a real Error (so existing (error: Error)
handlers still work) that also carries a typed code and optional details.
The codes are identical to the Flutter SDK:
import { MyazaKYC, KYCError } from "@myazahq/kyc-sdk-react";
<MyazaKYC
{...config}
onError={(error: KYCError) => {
switch (error.code) {
case "camera_permission_denied": /* ask the user to allow the camera */ break;
case "insufficient_credits": /* error.details = { required, balance, currency } */ break;
case "network_error":
case "upload_failed": /* shown only after automatic retries */ break;
}
}}
/>| code | When it fires |
| -------------------------- | ------------------------------------------------------------------- |
| network_error | Connection failure / timeout, after retries are exhausted. |
| invalid_api_key | Server returned 401. |
| insufficient_credits | Server returned 402. details = { required, balance, currency }. |
| upload_failed | A media upload failed, after retries are exhausted. |
| camera_permission_denied | The user denied (or the OS/browser blocks) camera access. |
| feature_disabled | Server returned 403 (ID type / feature not enabled for the org). |
| unknown | Anything else. |
Voice guidance is TTS output — it never records audio, so there is no microphone permission and no microphone error code.
Network resilience
Media uploads and the verify submission are wrapped in exponential-backoff retry
(with jitter), retrying only transient failures (network / timeout / 5xx);
terminal 4xx surface immediately. The UI shows "Reconnecting… / retrying
(n/3)…" between attempts, and onError fires only after retries are
exhausted (upload_failed for uploads, network_error for connectivity).
Camera permission
If the user denies camera access, the SDK shows a clear "camera access needed"
screen (with how to re-enable it) instead of hanging, and reports
camera_permission_denied to onError. Document capture additionally offers a
gallery-upload fallback unless allowDocumentUpload is false.
Liveness quality guards
- Multiple faces — if more than one face is in frame, the challenge pauses ("Make sure only your face is visible") and resumes automatically when only one face remains. This guards capture quality and a class of spoofing.
- Lighting — too-dark and too-bright (glare) conditions are detected live during liveness and document capture; the SDK shows guidance ("Move to a brighter area" / "Too bright — reduce glare") and discourages auto-capture until lighting is acceptable.
Documentation
Full documentation, configuration options, and webhook setup: identity.myaza.co/documentation/sdks.
