npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@myazahq/kyc-sdk-react-native

v2.5.0

Published

Myaza KYC SDK for React Native (Expo) — ID verification, liveness detection, and document capture

Downloads

628

Readme

@myazahq/kyc-sdk-react-native

Myaza KYC SDK for React Native (Expo) — ID verification with document auto-capture, eMRTD chip reading (NFC), active on-device liveness, email/phone OTP verification, questionnaires, proof of address, and full business (KYB) verification, for any supported country. Mirrors the web and Flutter SDKs feature-for-feature — including workflowId embeds — and calls the same Myaza KYC API server.

The SDK is a thin UI layer: it captures the user's data (ID number, document photos, a live selfie), uploads the media, and submits a verification request. All verification (OCR, facial comparison, gov-DB checks) happens server-side and is delivered asynchronously via webhook — the SDK is fire-and-forget.

Requirements

This library ships native code (an Apple Vision + Google ML Kit face detector, built as a react-native-vision-camera v5 / Nitro module), so it needs a custom native build and does not run in Expo Go.

| Requirement | Minimum | | --- | --- | | iOS deployment target | 15.1 | | Android minSdkVersion | 24 (Android 7.0) · compileSdk 34 · NDK 27.1 | | Expo SDK | 56 (React 19, React Native 0.85) | | React Native | 0.83+, with the New Architecture enabled (VisionCamera v5 / Nitro requires it; Expo SDK 56 enables it by default) | | Build toolchain | Xcode + CocoaPods (iOS) · JDK 17 for Android Gradle builds | | Runtime | A dev client or bare build — not Expo Go |

Peer dependencies to install in your app:

| Package | Range | Purpose | | --- | --- | --- | | expo | >=56 | Expo module runtime (the SDK uses several expo-* modules) | | react / react-native | >=19 / >=0.83 | — | | react-native-vision-camera | ^5 | Camera preview + capture | | react-native-vision-camera-worklets | >=5 | Frame-processor worklet runtime | | react-native-worklets | >=0.8 | Worklet runOnJS bridge for liveness | | react-native-nitro-modules | >=0.35 | Native module runtime for the face detector | | react-native-nitro-image | >=0.15 | Frame → image interop used by the detector | | react-native-safe-area-context | >=4 | Safe-area insets for the modal | | react-native-svg | >=15 | Liveness/overlay graphics |

Voice guidance is text-to-speech output only — the SDK never records audio, so no microphone permission is requested or required.

Installation

Expo app (managed / prebuild — recommended)

npx expo install @myazahq/kyc-sdk-react-native \
  react-native-vision-camera react-native-vision-camera-worklets \
  react-native-worklets react-native-nitro-modules react-native-nitro-image \
  react-native-safe-area-context react-native-svg

Add the config plugins to app.json. The SDK's plugin adds the iOS camera usage string + Android CAMERA/INTERNET permissions; the VisionCamera plugin wires up the camera + frame processors. Make sure the New Architecture is on (it is by default on Expo SDK 56):

// app.json
{
  "expo": {
    "newArchEnabled": true,
    "plugins": [
      ["react-native-vision-camera", { "enableMicrophonePermission": false }],
      "@myazahq/kyc-sdk-react-native"
    ]
  }
}

Then build a dev client (regenerates the native projects):

npx expo prebuild
npx expo run:ios                                  # iOS
JAVA_HOME=/path/to/jdk-17 npx expo run:android    # Android — needs JDK 17

The SDK plugin accepts an optional custom camera prompt: ["@myazahq/kyc-sdk-react-native", { "cameraPermission": "Your message…" }].

Bare React Native app (no Expo prebuild)

The SDK depends on a handful of expo-* modules, so install expo (the package — you don't need the managed workflow) and let it autolink:

# 1. Add the Expo module runtime to your bare app (one-time):
npx install-expo-modules@latest

# 2. Install the SDK + peers:
npm install @myazahq/kyc-sdk-react-native \
  react-native-vision-camera react-native-vision-camera-worklets \
  react-native-worklets react-native-nitro-modules react-native-nitro-image \
  react-native-safe-area-context react-native-svg \
  expo expo-image-manipulator expo-image-picker expo-speech expo-font \
  expo-glass-effect expo-application expo-crypto expo-device expo-localization

# 3. iOS pods:
cd ios && pod install && cd ..

Then add the native permissions yourself (the Expo config plugin only runs under prebuild):

  • iOS — add to ios/<App>/Info.plist:
    <key>NSCameraUsageDescription</key>
    <string>We use the camera to photograph your ID and capture a live selfie.</string>
  • Android — add to android/app/src/main/AndroidManifest.xml:
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />
  • Ensure the New Architecture is enabled (newArchEnabled=true in android/gradle.properties; RCT_NEW_ARCH_ENABLED=1 for the iOS Podfile install), and enable VisionCamera frame processors / the worklets babel plugin — see the VisionCamera setup docs.

The face-detector native module (Nitro HybridObject + Android lib loader) is autolinked via React Native / Expo autolinking — no manual linking required.

Usage

<MyazaKYC /> renders a "Verify Identity" trigger plus the full modal flow. Pass children (a string) to relabel it. For a fully custom trigger, use the useMyazaKYC() hook.

Recommended — mount a workflow

Build the flow once in the Myaza dashboard as a workflow, then mount it by id. The country, ID types, capture steps, add-ons, branding and copy all come from the workflow, so changing the flow is a re-publish in the dashboard rather than a new app build and an app-store review. See Workflows.

import { MyazaKYC } from '@myazahq/kyc-sdk-react-native';

export default function VerifyScreen() {
  return (
    <MyazaKYC
      apiKey="pk_live_xxx"          // prefix selects the env: pk_test_ → sandbox
      workflowId="wf_AbC123dEf456"
      // Runtime data — a workflow is a shared template and cannot carry any of it.
      userId="usr_123"
      userData={{ firstName: 'Jane', lastName: 'Doe' }}
      metadata={{ orderId: 'ord_456' }}
      onSubmit={(submission) => console.log('Submitted!', submission.verificationId)}
      onError={(err) => console.warn('SDK error:', err.code, err.message)}
      onClose={() => console.log('Modal closed')}
    >
      Verify my identity
    </MyazaKYC>
  );
}

userData is worth passing. It is the name you believe the user has, and it is compared against the name read off their document — that comparison is what produces dataMatch on the verification. It cannot live on the workflow: userId, userData and metadata are per-user runtime values, and a workflow is a template shared by every visitor, so these stay in code even when everything else moves to the dashboard.

Or configure everything in code

Skip the workflow and pass the flow's shape as props. Useful for a quick start or a single fixed flow; anything you'd change later means shipping a new build.

import { MyazaKYC } from '@myazahq/kyc-sdk-react-native';

export default function VerifyScreen() {
  return (
    <MyazaKYC
      apiKey="pk_live_xxx"          // prefix selects the env: pk_test_ → sandbox
      country="NG"
      idTypes={['passport', 'drivers-license', 'bvn', 'nin', 'pvc']}
      userData={{ firstName: 'Jane', lastName: 'Doe' }}
      enableSelfie
      enableDocumentCapture
      enableLiveness
      showThemeToggle
      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.",
      }}
      userId='usr_123'
      metadata={{ orderId: 'ord_456' }}
      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.warn('SDK error:', err.code, err.message)}
    >
      Verify my identity
    </MyazaKYC>
  );
}

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 | string (ISO-2) | — | Country whose ID types are offered. Required unless workflowId is set (the workflow carries its own country). Any ISO-2 code works — 'NG' \| 'GH' \| 'KE' \| 'ZA' \| 'CI' keep autocomplete and client-side ID-number validation; other countries render their ID types from the server. | | workflowId | string (wf_…) | — | Run a published Workflow built in the dashboard. Workflow config wins over overlapping props. See Workflows. | | countries | WorkflowCountry[] | — | Multi-region — more than one entry inserts a country-select step between consent and ID type. Usually from a workflow. | | idTypes | IdType[] | all allowed for org | Subset of ID types to offer; must be valid for country. | | userId | string | — | Your reference for the person being verified. Not matched during verification — it correlates repeat checks of the same user onto one identity so results map back to your record. Prefer this over putting a user id in metadata. | | 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 gallery 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. | | livenessMode | 'gestures' \| 'flash' \| 'both' | 'gestures' | How liveness is proven. See Liveness. | | flashSequenceLength | number (2–5) | 4 | Colours in the flash sequence, for 'flash' / 'both'. | | 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. | | emailVerification | EmailVerificationConfig | off | Email OTP step after consent. See Optional steps. | | phoneVerification | PhoneVerificationConfig | off | Phone OTP step (SMS or WhatsApp). See Optional steps. | | proofOfAddress | ProofOfAddressConfig | off | Proof-of-address upload after capture. See Optional steps. | | questionnaire | QuestionnaireConfig | off | Compliance declarations before submission. See Optional steps. | | nfc | NfcConfig | off | eMRTD chip read — native on this SDK, and the strongest assurance level available. See Optional steps. | | subjectType | 'individual' \| 'business' | 'individual' | Business (KYB) flows require a published KYB workflow. See Business (KYB) flows. | | business | WorkflowBusinessConfig | — | KYB registry configuration. Normally supplied by a resolved workflow. | | deviceIntelligence | boolean | true | Device + IP fraud signals (multi-accounting, emulator, velocity). Billed per verification; false disables the analysis, its charge, and the SDK's fingerprint collection. | | requireMobileDevice | boolean | false | Refuse to run on a desktop/laptop — relevant because React Native also targets desktop runtimes and emulators. The server re-checks and rejects with mobile_device_required. | | showThemeToggle | boolean | true | Show a light/dark toggle inside the modal header. When false, the flow stays on appearance.theme. | | disableClose | boolean | false | Hide the close (X) and block all user dismissal (X, Android back, iOS swipe-down). The flow can then only be closed programmatically via useMyazaKYC().close(). | | 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. Verification outcomes never come through here. | | onClose | () => void | — | Called when the user closes the flow. | | children | string | Verify Identity | Trigger label. Defaults to Verify with {companyName} when companyName is set, else Verify Identity. | | disabled | boolean | false | Disable the trigger. |

Environment

There is no environment prop — the SDK derives the environment (and the base URL) from the API key prefix, the single source of truth:

| Prefix | Environment | Base URL | | ---------- | ----------- | --------------------------------- | | pk_test_ | sandbox | https://trust.myaza.app | | pk_live_ | production | https://trust.myaza.app |

An unrecognized or malformed key throws at setup (it never silently defaults).

Workflows

The recommended integration (see Usage): build the flow in the Myaza dashboard and reference it by id —

<MyazaKYC apiKey="pk_live_xxx" workflowId="wf_abc123" userId="usr_123" />
  • Workflow config wins over any overlapping prop — country, ID types, step toggles, appearance, copy. Set them in the builder, not in code.
  • Runtime data always comes from your code: userId, userData, metadata, and every callback.
  • country becomes optional, because the workflow carries it.

This is the recommended way to drive the optional steps below: compliance teams change the flow in the dashboard without shipping a new app build — which matters far more on mobile than on web, where a redeploy is instant and an app store review is not.

Optional steps

Steps that are off unless configured. Each is normally switched on in the dashboard workflow builder (so it rides workflowId), but every one can also be passed directly as a prop.

The flow runs them in this order:

consent → email-verification → phone-verification → country-select → id-type
       → id-input / document-capture → nfc → liveness → proof-of-address
       → questionnaire → submitted

| Prop | Shape | What it adds | |---|---|---| | emailVerification | { enabled?, required?, codeLength?, maxAttempts?, inputStyle? } | Email OTP right after consent. required: false adds a "skip for now". codeLength 4–8 (default 6), maxAttempts 1–5 (default 3). | | phoneVerification | same, plus { channels?, defaultCountry? } | Phone OTP. channels defaults to ['sms']; add 'whatsapp' to offer it. | | proofOfAddress | { enabled?, documentTypes?, otherLabel?, maxAgeDays? } | Upload a utility bill, bank statement, tenancy agreement, or other document. maxAgeDays is the recency window (default 90). | | questionnaire | { enabled?, title?, description?, fields } | Compliance declarations before submission. Field type is one of text, number, money, select, multiselect, boolean, date. | | nfc | { enabled?, idTypes?, allowSkip? } | Reads the passport/ID chip (eMRTD). |

The chip read is a real, native capability on this SDK — unlike the web SDK, which cannot do ISO-DEP from a browser. It gives the strongest assurance level available. A device with no NFC radio skips the step automatically; allowSkip adds a manual escape hatch for a chip that will not read, revealed after a failed attempt rather than offered on arrival. Skipping never fails the verification — the chip result is a soft sub-result.

Answers and outcomes arrive in the verification webhook (data.questionnaire, data.emailVerification, data.phoneVerification, data.proofOfAddress).

<MyazaKYC
  apiKey="pk_live_xxx"
  country="NG"
  nfc={{ enabled: true, allowSkip: true }}
  phoneVerification={{ enabled: true, channels: ["sms", "whatsapp"] }}
  questionnaire={{
    title: "A few final questions",
    fields: [
      { key: "source_of_funds", label: "Source of funds", type: "select", required: true,
        options: [{ value: "salary", label: "Salary" }, { value: "business", label: "Business income" }] },
    ],
  }}
/>

Business (KYB) flows

When a resolved workflow's config carries subjectType: 'business', the SDK runs a company-verification flow instead of the individual one: a registry lookup (country, product, registration number/name), and — when the workflow configures them — a company profile, directors & owners, and supporting-document uploads.

KYB is workflow-required: there is no prop-only business flow, because the server rejects a business submission that does not reference a published KYB workflow.

If the workflow asks the submitter to verify their own identity, the ordinary individual capture leg runs afterwards for them, and the success screen can hand back invite links for any directors or owners who need their own check.

Trigger component & hook

<MyazaKYC /> renders a styled trigger that opens the modal. Beyond the config props it accepts children (a string label) and disabled:

<MyazaKYC {...config} disabled={!ready}>
  Start verification
</MyazaKYC>

For a fully custom trigger (your own Pressable, an icon, a list row, or to open the flow programmatically), use the useMyazaKYC() hook:

import { Pressable, Text } from 'react-native';
import { useMyazaKYC } from '@myazahq/kyc-sdk-react-native';

function CustomTrigger() {
  const { open, close, isOpen, currentStep } = useMyazaKYC({
    apiKey: 'pk_live_xxx',
    country: 'NG',
    onSubmit: (s) => console.log('submitted', s.verificationId),
  });

  return (
    <Pressable onPress={open}>
      <Text>Verify your identity</Text>
    </Pressable>
  );
}

useMyazaKYC(config) returns { open, close, isOpen, currentStep }. close() is the only way to dismiss the flow when disableClose is set.

Appearance & theming

Pass an appearance object to brand the flow. Because the UI is token-driven, setting one color cascades to all of its shades (hover/selected/focus states included). Unset colors keep the built-in light/dark defaults.

| Field | Type | Description | | ------------------ | ------------------- | -------------------------------------------------------------------------------- | | primaryColor | string | Brand color — buttons, selected states, progress, the shield hero. 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 trigger ("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, the avatar is hidden.
  • 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.

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 with a typed code, a human-readable message, and optional details. The codes are identical to the web and Flutter SDKs:

import { MyazaKYC, type KYCError } from '@myazahq/kyc-sdk-react-native';

<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 blocks) camera access. | | feature_disabled | Server returned 403 (ID type / feature not enabled for the org). | | unknown | Anything else. |

Verification outcomes (identity not found, document mismatch, …) never come through onError — they arrive asynchronously via webhook / GET /api/kyc/status/:id.

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 a top toast while retrying, 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 an Open Settings action) 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; the SDK shows guidance ("Move to a brighter area" / "Too bright — reduce glare") and blocks auto-capture until lighting is acceptable.

Liveness

Active, challenge-based liveness (nod / turn / blink / smile — 2 randomly chosen per session). Face detection runs on-device, natively:

  • iOS — Apple Vision (VNDetectFaceLandmarksRequest).
  • Android — Google ML Kit (native Gradle dep, Android-only — so no cross-platform ML Kit iOS pod, and the SDK still builds on Apple-Silicon iOS simulators).

Both run as a react-native-vision-camera v5 Nitro frame processor: the camera frame never crosses the JS bridge. The selfie is auto-captured once challenges pass (anti-spoofing — never user-triggered), and a short liveness video is recorded and uploaded best-effort.

Documentation

Full documentation, configuration options, and webhook setup: trust.myaza.co/documentation/sdks.

License

MIT © Flitstack Technologies Inc.