Adding identity verification to a React Native or Flutter app
The component call is the easy part. A mobile integration is mostly native build setup, camera permission, mounting a dashboard workflow so changes skip the app store, and keeping results on your server rather than on the phone.

Charles Archibong, Co-founder
· 7 min read

Key takeaways
- The React Native SDK ships native code, so it needs a dev client or bare build and does not run in Expo Go.
- Mount a published workflow by ID so flow changes are a re-publish rather than an app-store release.
- onSubmit means the verification was accepted; read the result on your backend with a secret key.
- NFC chip reading works only in the mobile SDKs, and a phone without NFC skips the step without failing.
Integrating identity verification into a React Native or Flutter app is a few lines of code and a fair amount of native setup. The Myaza Trust SDKs render the whole flow (consent, ID selection, document capture, selfie and active liveness) and submit it for you. What you own is the build configuration, the camera permission, the key you ship, and what your app does after the applicant presses submit.
The approach that ages best is to mount a workflow built in the dashboard, pass only per-user data from code, and read results on your server. That way a change to the flow is a re-publish rather than an app-store release, and no identity data needs to come back to the phone. The rest of this article covers each step and the mobile-specific decisions along the way.
What are you actually adding to the app?
Both SDKs run on-device native liveness: Apple Vision on iOS and Google ML Kit on Android. That is the reason for most of the setup. Liveness here is Presence Intelligence (a live selfie plus randomised gesture challenges, a screen-flash colour sequence, or both), and it runs on camera frames in native code rather than in a web view.
The two SDKs are at feature parity with the web SDK for the core flow, workflow embedding, the capture add-ons (contact OTP, proof of address, questionnaire), business verification and any global documents country. They add one thing the web cannot do: reading the chip in e-passports and chip ID cards over NFC.
What do the builds require?
The minimums, from the React Native and Flutter SDK pages:
Requirement | React Native | Flutter |
|---|---|---|
Package |
|
|
Framework | React Native 0.83+ with the New Architecture; Expo SDK 56 | Flutter 3.27 (Dart 3.6) |
iOS deployment target | 15.1 | 13.0 |
Android | 24 | 21 |
Runtime | A dev client or bare build, not Expo Go | Any Flutter build |
Workflow mounting from | SDK 2.1.0 | SDK 2.2.0 |
The React Native line is the one that catches teams out. The SDK uses a VisionCamera v5 (Nitro) frame processor, which is native code, so it cannot run inside Expo Go. Plan for a custom dev client from the first day of integration, not the day before release.
How do you install it?
For an Expo app on React Native, install the SDK and its peers, add the config plugin, and build a dev client:
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// app.json
{
"expo": {
"plugins": ["@myazahq/kyc-sdk-react-native"]
}
}npx expo prebuild
npx expo run:ios # or: npx expo run:androidDo not add VisionCamera itself to plugins. Version 5 ships no config plugin, and listing it makes expo prebuild fail. The Myaza plugin adds the iOS camera usage string and the Android CAMERA and INTERNET permissions for you. Bare React Native apps add the Expo module runtime with npx install-expo-modules@latest and declare the permissions by hand; the SDK page has the exact commands.
For Flutter, add the dependency and run flutter pub get:
dependencies:
myaza_kyc_sdk_flutter: ^2.2.0Then declare the camera permission yourself: NSCameraUsageDescription in ios/Runner/Info.plist, and CAMERA plus INTERNET in AndroidManifest.xml.
Which permissions will your users see?
Camera, always. Location, only if a workflow uses the Address Intelligence step. The React Native config plugin adds the location strings by default (pass { "location": false } to opt out); in Flutter and bare React Native you add them yourself. On iOS the app crashes on the location request if NSLocationWhenInUseUsageDescription is missing, so add it whenever an address step is possible. Microphone, never: voice guidance is text-to-speech output and the SDK does not record audio. That last point is worth putting in your app-store privacy answers.
Which optional React Native modules matter?
Four expo-* modules are optional peers, and each buys a capability. The one most teams miss is expo-document-picker: without it, the proof-of-address and business document steps accept a camera capture only, so a PDF bank statement cannot be submitted at all. expo-device improves the device fingerprint used by device intelligence, expo-application records your app's version on the submission, and expo-localization gives the SDK the device's region.
How should you mount the flow?
Mount a published workflow by its ID. The workflow carries the country, ID types, capture steps, branding and copy, and it can carry server-side decision rules too. React Native:
import { MyazaKYC } from "@myazahq/kyc-sdk-react-native";
export default function VerifyScreen() {
return (
<MyazaKYC
apiKey="pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
workflowId="wf_AbC123dEf456"
userId="user_42"
userData={{ firstName: "Jane", lastName: "Doe" }}
metadata={{ requestId: "order_1001" }}
onSubmit={(submission) => console.log("submitted", submission.verificationId)}
onError={(err) => console.error(err.code, err.message)}
onClose={() => console.log("closed")}
>
Verify Identity
</MyazaKYC>
);
}Flutter opens the same flow as a modal sheet. Note that context is a named parameter and the callbacks sit beside config, not inside it:
MyazaKYC.show(
context: context,
config: const MyazaKYCConfig(
apiKey: 'pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
workflowId: 'wf_AbC123dEf456',
userId: 'user_42',
userData: UserData(firstName: 'Jane', lastName: 'Doe'),
metadata: {'requestId': 'order_1001'},
),
onSubmit: (submission) => debugPrint('Submitted: ${submission.verificationId}'),
onError: (error) => debugPrint('Error: ${error.code}: ${error.message}'),
onClose: () => debugPrint('KYC closed'),
);Three details in those snippets are deliberate.
The key is publishable. Only a pk_ key belongs in an app. Flutter derives the environment from its prefix (pk_test_ is Sandbox, pk_live_ is Production), so switching to production is a key change. A publishable key can start a verification but cannot read its result.
userId, userData and metadata stay in code. A workflow is a template shared by every visitor, so it cannot carry per-user values. userData is worth passing: it is the name you believe the user has, compared against the name read off their document to produce dataMatch. Leave it out and dataMatch comes back null.
metadata.requestId should come from your own record. It is the idempotency key for the submission, so derive it from the onboarding record it belongs to rather than generating a new one each time the screen mounts.
Why a workflow rather than props? Both SDKs accept the full configuration as props, and that works for a fixed flow. But on mobile every change to props is an app release and a review cycle, and old app versions stay installed for months. With a workflow, adding an ID type or tightening a face-match threshold reaches every installed version the next time the flow opens. The Workflows product page describes the builder.
What happens after the applicant submits?
onSubmit fires when the server accepts the verification. It is not the result. The checks run asynchronously, and the outcome reaches your backend by webhook, or by a secret-key call to GET /api/kyc/verifications/:id. onError fires for technical errors only (network, authentication, credit, upload), never for a declined verification.
So the pattern on mobile is: on onSubmit, send the verificationId to your backend, show a "we are checking your details" screen, and let your backend tell the app what to show next when the webhook arrives. Do not fetch the full result from the app. It requires a secret key, and a secret key in an app binary is a secret key someone else has.
When does NFC chip reading make sense?
When your applicants hold e-passports or chip ID cards and you want the strongest assurance level. The mobile SDKs read the chip over its secure channel, and the server runs passive authentication: it rechecks each data group's hash against the chip's signed security object and verifies that the signer chains to a trusted issuing-state certificate (CSCA). A chip that proves to be tampered fails the verification with chip_not_authentic.
Four limits to design around, from the NFC chip documentation:
Coverage follows certificates. The chip step is offered only for countries on Myaza's certificate list, which covers 138 countries including Nigeria, Ghana, Kenya, Egypt, Ethiopia and Senegal.
GET /configreportssupportsNfcper ID type for your account.Some chips cannot be read by anyone. The South African smart ID card, the Emirates ID and Malaysia's MyKad use proprietary chip applications. Those documents still verify through document checks.
Phones without NFC skip the step, and skipping never fails a verification. A missing or unreadable chip falls back to the document and government-record checks.
The chip step runs after the document scan, because the key that opens the chip's secure channel is derived from the document's machine-readable zone.
Which chip-capable IDs offer the step, and whether users may skip it, are workflow settings in the builder's NFC panel.
How do you keep the app size reasonable?
On Android, face detection and text recognition use Google ML Kit, and the SDK depends on the Play Services variants that download their models on first use rather than bundling them. Bundling them adds a sizeable download to every device, which is why the fetched variants are the default. The trade-off is that Play Services variants do not work on devices without Google Play Services, such as Huawei. If you ship to those, set ext { myazaKycBundledMlKit = true } in your root build.gradle to bundle the models instead.
Shipping an Android App Bundle rather than a universal APK, and enabling R8 with resource shrinking, reduce the download further. The SDK ships its own consumer rules for R8.
Checklist before release
React Native: a dev client or bare build in CI, never Expo Go; New Architecture enabled.
Camera permission declared; location string declared if any workflow collects an address; no microphone permission.
expo-document-pickerinstalled if any workflow collects documents as files.A published workflow mounted by ID;
userId,userDataand a stablemetadata.requestIdpassed from code.A
pk_test_key in development builds and apk_live_key in release builds, never a secret key.onSubmithands theverificationIdto your backend; results come from webhooks.NFC coverage checked against
supportsNfcfor the countries you serve.
Test the whole path in Sandbox first with published test IDs, then swap the key. The developer hub links both SDK guides.
Sources

Charles Archibong
Co-founder
Charles Archibong co-founded Myaza Trust. He writes about identity verification, financial technology, and the practical work of building trusted digital services.


