React Native SDK
The React Native SDK mirrors the web SDK’s core API (same props, same callbacks) but runs on-device, native liveness: Apple Vision on iOS and Google ML Kit on Android, via a react-native-vision-camera v5 (Nitro) frame processor. Because it ships native code, it needs a custom native build and does not run in Expo Go. For shared concepts (supported countries, branding, results, and errors), see Client SDKs.
Feature availability. This SDK is at full parity with the web and Flutter SDKs: workflow embedding (workflowId), the capture add-ons (contact OTP, proof of address, questionnaire), business (KYB) verification, any Global Documents country, plus NFC chip reading, which the web SDK can’t do at all (browsers can’t talk to a passport chip). See Feature availability.
Requirements
Peer dependencies to install in your app: expo (≥56), react (≥19), react-native (≥0.83), react-native-vision-camera (v5), react-native-vision-camera-worklets (≥5), react-native-worklets (≥0.8), react-native-nitro-modules (≥0.35), react-native-nitro-image (≥0.15), react-native-safe-area-context (≥4), react-native-svg (≥15).
Install
Pick the path that matches your project.
Expo app (managed / prebuild, recommended)
Add the config plugin to app.json (it adds the iOS camera-usage string and Android CAMERA / INTERNET permissions), then build a dev client:
VisionCamera v5 ships no config plugin (v4 did), so it takes no plugins
entry. Listing it makes expo prebuild fail with
Cannot find module '.../lib/VisionCamera', because Expo loads the package’s
main entry as a plugin. The camera permission and usage strings come from the
Toden plugin; pass cameraPermission to change the iOS wording.
The SDK plugin accepts an optional custom camera prompt: ["@myazahq/kyc-sdk-react-native", { "cameraPermission": "Your message…" }]. It also adds the location permission strings by default (the Address Intelligence step’s “Use my current location” shortcut and attest fix; foreground only, and never required to finish the flow) — pass { "location": false } to opt out if none of your workflows collect an address, or { "locationPermission": "Your message…" } to customise the iOS prompt.
Optional modules
Four expo-* modules are optional peers. The SDK loads each one lazily and carries on without it, so nothing crashes if you skip them, but each one is a capability rather than a detail:
react-native-webview is an optional peer. With it installed, the address step renders the Google map and the Street View entrance framer inside your app; without it the built-in map and the entrance photo remain, and nothing else changes. Address fields the workflow marks required are enforced in the app (Continue opens the details sheet naming what is still needed), so a submission is never refused for a missing field at the end of the flow.
Bare React Native app (no Expo prebuild)
The SDK uses a few expo-* modules, so add the Expo module runtime (you don’t need the managed workflow), then install the SDK and its peers:
Then add the native permissions manually (the config plugin only runs under prebuild):
If your workflows use the Address Intelligence step, also add the location strings (iOS crashes on the permission request without the usage string):
The native face-detector module (a VisionCamera v5 Nitro HybridObject plus its Android lib loader) is autolinked, so no manual linking is needed. Ensure the New Architecture is enabled and the worklets/frame-processor build is set up per VisionCamera’s setup docs (it relies on react-native-worklets / react-native-vision-camera-worklets, installed above).
Rebuild your app after updating the SDK. The SDK ships its own native views (the blurred surround of the full-screen liveness camera, and on iOS the guard that keeps a finger on the signature pad from scrolling the screen or dragging the sheet down). They are autolinked, but a JavaScript-only update cannot add them: run npx expo prebuild (or cd ios && pod install) and build the app again whenever you move to a new SDK version. An app that only reloads its bundle keeps working, with a plain tint in place of the blur.
Presence reporting (Address Intelligence): required if you use presence verification
Presence verification needs setup in your app. When the flow is submitted the SDK sends the first report and turns on background monitoring, which only works once your app has the background location setup below. Every other report has to come from your app calling the reporter when it opens. If people do not open your app daily, also turn the evening reminder webhook into a push notification. If your app has none of these, every presence check collects one day and ends inconclusive. The full checklist is on the Address Intelligence page.
When a workflow enables presence verification, the SDK stores the confirmed pin on the phone under the userId you pass (so pass one) and sends the first report when the flow is submitted. After that, call the reporter when your app starts and every time it comes back to the foreground, for the signed-in user:
It is safe to call on every open for every user: for a user this phone holds no address for it returns no_pin straight away without touching location, and several reports on one day count once. What each reason means, and what to do about it, is in the reason table. With the reporter alone a person needs to open your app at home on 5 different evenings, or on 9 different days, within the default 10-day window; background monitoring below needs 3 nights or 5 days and happens without them doing anything.
It never throws and never blocks startup. The geofence is evaluated on-device: only the derived day + night flag is transmitted, never a coordinate. A fix outside the fence sends nothing; a mock-location fix is reported flagged. clearPresencePin(externalUserId) drops the stored pin (sign-out, or after the watch resolves). Stored pins self-expire after 45 days for bounded checks; a pin captured under always-on monitoring never expires until revoked.
Background monitoring (OS geofencing)
People already being checked. The SDK switches background monitoring on when a flow is submitted. People who verified on an older version, and people on always-on monitoring, never pass that step again. From 3.5.0 the reporter does it for them: when their check is still running and background monitoring is not on, a report switches it on. The person sees the system’s “allow all the time” prompt once, and the SDK never asks again from a report. If they already allowed it in Settings there is no prompt. Your app still needs the background location setup below. To show your own explanation first, pass autoBackground: false and call enableBackgroundPresence() when you are ready.
The stronger tier: the OS wakes the SDK on fence crossings around the stored pin, app closed or not. Entries stamp a timestamp; exits fold the dwell into per-day aggregates on the phone and flush them, the same privacy floor as the foreground tier.
It is on by default. When a presence flow is submitted, the SDK asks the person to allow location all the time and arms the geofence itself; you do not call anything for that. What it cannot do is set up your app, so add these three once:
- Install the optional peer:
npx expo install expo-task-manager. - Declare background location via the config plugin:
["@myazahq/kyc-sdk-react-native", { "location": "always" }]. This changes your app’s store review posture; the Background Location Declarations page carries the ready-to-paste Play Console and App Review texts. - Register the task at your app’s root module, before the component tree:
Without all three the SDK skips background monitoring, asks for nothing, and your app’s reporter is the only source of evidence. To turn it off for a workflow, switch off Background monitoring on the Address step’s Presence tab.
You can still arm or disarm it yourself, for example to offer it again later to someone who refused:
When evidence is sent. A stay is reported when the person leaves the area, or at a “still here” check-in: the first 35 minutes after they arrive, then every three hours. Nothing is sent in between, so the day count does not move while someone simply stays at home. A check-in happens when the app is opened and, if you install the optional expo-background-task (npx expo install expo-background-task, plus its config plugin), about every 40 minutes in the background. Android and iOS decide when a background task really runs.
presenceStay('user_42') returns the stay the phone has open, { since, nextReportAt } in epoch milliseconds, or null. Use it to tell the person a report is on its way.
Rebuild after installing an optional Expo package. expo-task-manager and expo-background-task contain native code. If the JavaScript is installed but the native module is not in the app build (a package added without a rebuild, or one hoisted by a monorepo), SDK 3.4.0 and older closed the app in a release build when background monitoring was switched on. Later versions check for the native module first and skip the feature. Either way the feature only works after a rebuild.
not_registered means step 3 is missing. disableBackgroundPresence() disarms the fence. A refusal at any step leaves the foreground tier working exactly as before: the tiers degrade, never break.
The Android foreground service
On phones whose battery managers drop geofence transitions (Tecno, Infinix, Xiaomi and friends), a persistent notification is what keeps the check alive. Opt-in, Android only, on the same location: "always" plugin setting, which also declares the FOREGROUND_SERVICE and FOREGROUND_SERVICE_LOCATION permissions. registerBackgroundPresence() at the root already defines its task:
A low-power fix every ten minutes becomes the same enter/exit spans the geofence folds, on the same stored state; the queue flushes while the process is alive; a dropped fence is re-armed. disableForegroundService() stops it. See Address Intelligence for the reasoning.
Which tier is running?
Permissions get revoked in Settings and nothing tells the app. Ask, and offer the only honest road back:
To show the person where the check stands, poll GET /api/kyc/address/presence/:externalUserId with the publishable key; see Address Intelligence.
Voice guidance is text-to-speech output. The SDK never records audio, so no microphone permission is requested or required (enableMicrophonePermission: false).
Usage
<MyazaKYC /> renders a “Verify Identity” trigger plus the full-screen flow. The API is identical to the web SDK, with the same props and callbacks. Unlike the web SDK there is no stylesheet to import: styling is built in.
Recommended: mount a workflow
Build the flow once in the dashboard as a workflow, then mount it by id. The country, ID types, capture steps, branding and copy all come from the workflow, so changing the flow is a re-publish rather than an app release, which matters even more on mobile, where a redeploy means an app-store round trip. Requires ≥ 2.1.0 (the 2.0.x line silently ignores the id).
userData is worth passing. It is the name you believe the user has, and it is compared against the name read off their document, and that comparison is what produces dataMatch on the verification. Leave it out and the check simply never runs: there is nothing to compare the document against, and dataMatch comes back null.
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 an app release.
The SDK accepts the same props as the web SDK’s props table, including workflowId, livenessMode, deviceIntelligence, consentStep (skip the welcome screen when your app has already asked for consent), and the capture add-ons (contact OTP, proof of address, questionnaire, NFC). The one exception is className (React Native has no class names); style the trigger by passing style, or render your own trigger with the hook.
disableClose blocks user dismissal on native too: the iOS swipe-down and the Android back button. Because the built-in <MyazaKYC /> trigger has no external close handle, pair disableClose with the useMyazaKYC() hook and call its close() to dismiss the flow yourself.
Biometric re-authentication
A workflow on the biometric-authentication scope confirms it is really your user, matching a live selfie against the face you enrolled. Three options on the workflow, plus your own words for its screens, shape how the React Native flow behaves (the web and Flutter SDKs honour the same three). All three live on the workflow’s Presence Intelligence panel in the builder, under Flow, so they need no code change; a prop-configured mount can pass the same biometric block.
- Where the verdict lands: In the app and on your webhook (the default) keeps the person on one loading screen from the moment the selfie is taken until the check settles, polling the status endpoint for up to a minute, then shows whether it was them; your app hears the verdict in
onResultand your webhook receives it as on every other flow. In the app only is the same wait, but the server sends no webhook for that check at all (verification.startedand the terminal events included), so the verdict is your app’s alone. Webhook only shows the ordinary submitted screen and leaves the verdict to your webhook. Enrolment workflows record the reference and have no verdict to deliver, so they never wait. - Selfie review: whether the captured selfie is shown back with Retake and Continue before it is sent. Off by default on the biometric scopes: a face check is a few seconds long, and a review screen is a stop in the middle of it. Turn it on when you want the person to approve the photo first.
- Done button: whether the final screen carries a Done button. On by default. Turn it off when your app closes the flow itself from
onResult(or fromonSubmiton a webhook delivery); the screen then stays until your app dismisses the SDK. - Screen copy: your own title and description for the screens the person sees, on the workflow’s Presence Intelligence panel under Face check screens: the loading screen while the check runs (“Checking it’s you”) and, on a verdict delivered in the app, the verified screen (“You’re verified”) and the not-verified screen (“We couldn’t confirm it’s you”). Leave a field empty to keep the default;
{firstName}and{lastName}fill from theuserDatayou pass, as on the welcome and success screens. A not-verified description replaces the reason the server sends. Enrolment offers the loading screen only. In code the same block isbiometric.copy(waiting,verified,declined, each withtitleanddescription).
When the flow waits, the SDK fires a callback with the verdict:
onSubmit still fires the moment the check is submitted. onResult fires once when the wait settles, never when it times out, and carries the state and reason pair only; scores and other result data stay behind your secret key. Treat it as a courtesy to your UI: the webhook (where the flow sends one) and GET /api/kyc/verifications/:id remain the record, and a decision your app enforces should rest on those, not on a value a client reported.
App size
The SDK adds native machine learning to your app, and that is where the weight sits. The defaults are already the small ones, but two of the four levers below are yours to pull and they are worth more than everything the SDK does on its own.
On-device models are fetched, not bundled. Face detection and text recognition both run on Google ML Kit on Android, and the SDK depends on the Play Services variants, which download their models on first use. Measured on a real integrator’s release APK, the bundled pair cost 18.5 MB per device (arm64: text 10.55 MB, face 7.95 MB) plus .tflite files in assets/, which ship to every device because assets are not split by architecture. Fetched, that is about 0.4 MB of shims.
The SDK starts both downloads the moment the flow opens, so they overlap the consent and ID-type screens. If a model has not arrived by the time it is needed, the step says so rather than failing quietly: liveness waits and explains, and the MRZ scanner tells the person the printed code cannot be read and lets them continue without the chip.
The trade is real. The Play Services variants need Google Play Services, so they do not work on Huawei or bare AOSP devices. If you ship to those, put this in your root build.gradle and you get fully-offline models back, at 18.5 MB per device:
Ship an App Bundle, or filter your architectures. Native libraries dominate the rest of the download, and a universal APK carries every architecture at once. An .aab lets Play deliver only the one a device needs. If you must ship an APK, name the architectures your users actually have:
Turn on R8 and resource shrinking. The SDK ships its own consumer rules, so you do not need to work out which of its classes are constructed by name:
Use Expo SDK 54 or newer. Its default template builds smaller than earlier ones, and the SDK’s peer range assumes it.
Programmatic control
For a custom trigger, drive the flow with the useMyazaKYC() hook:
The flow advances through the same KYCStep values as the web SDK: consent → id-type → id-input → document-capture → liveness → submitted.