@veryai/react-native-sdk
v1.0.66
Published
React Native wrapper for Very SDK - Palm biometrics verification
Downloads
1,354
Readme
React Native SDK Integration
Full documentation: https://very.org/docs/native-sdk/integration
Installation
npm install @veryai/react-native-sdkUsing Expo? The steps below are for bare React Native — see Expo instead.
For iOS, install the native dependency:
cd ios && pod installAdd camera permission to Info.plist:
<key>NSCameraUsageDescription</key>
<string>Camera access is needed for palm biometric verification.</string>Published on npm.
Expo
The SDK works with the Expo managed workflow. You never need to run pod install
yourself or commit ios/ and android/ — expo prebuild and EAS Build do that
for you.
Two things are non-negotiable:
- A custom dev client. The SDK carries native Swift/Kotlin code and the PalmID
native matcher, so Expo Go cannot load it. Build a dev client with
npx expo run:ios/npx expo run:android, oreas build --profile development. - A physical device. Palm capture needs a real camera and the native matcher; simulators and emulators will not work.
There is no config plugin to install — the package does not ship one, and does not need one.
iOS
Declare the camera usage string in app.json. expo prebuild writes it into the
generated Info.plist, so this is the only native configuration the host app owes
the SDK:
{
"expo": {
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "Camera access is required for palm biometric verification."
}
}
}
}Android
Nothing to configure. The SDK's AAR manifest already declares CAMERA,
INTERNET, and ACCESS_NETWORK_STATE, and manifest merge folds them into the
host app.
If you forget the usage string
authenticate() fails on iOS with error 6105 rather than proceeding, because
AVCaptureDevice.requestAccess would otherwise terminate the process
unrecoverably. How that surfaces depends on the build configuration:
| Build | What you see |
| ----- | ------------ |
| Release | the 6105 error through the normal SDK error callback |
| Debug (including a default dev client) | an assertion trap, plus the same message in the log |
A dev client is a Debug build by default, so during development expect the trap.
Either way the fix is the same: add the key to app.json and rebuild — a JS
reload is not enough, since the value lives in the compiled Info.plist.
There is no Android equivalent of 6105; Android grants camera access through the runtime permission model instead.
Slim mode is not available in a pure managed workflow
Switching to slim asset loading edits ios/Podfile and android/app/build.gradle,
both of which are prebuild output that gets regenerated. Using it means either
committing the native directories or writing a config plugin with dangerous mods.
Stay on the default bundled mode unless binary size is a hard constraint.
Reference project
examples/ExpoNewArchExample is a working
managed setup (Expo SDK 54, RN 0.81, New Architecture). CI runs
expo prebuild --clean against it and builds both platforms natively on every PR
that touches the SDK or the examples.
Asset Loading: Bundled vs Slim
The SDK ships with the palm-recognition native asset bundled inside it by default — your app works offline, no first-scan download wait. If you'd rather keep your binary small and have the SDK fetch the asset from CDN on first scan, you can opt into slim mode per platform.
| Mode | Adds to app size | First-scan UX | Works offline | | ------ | ----------------------------------------- | ---------------------------- | ------------- | | Bundled (default) | ~18 MB per ABI (Android), ~8 MB (iOS) | instant | yes | | Slim | nothing | one-time download (5–15 s) | no (first scan only) |
Cached assets persist across app launches; the download only happens once.
iOS — switching to slim
Step 1 — disable RN auto-linking for this package only. Add or edit react-native.config.js at your project root:
module.exports = {
dependencies: {
'@veryai/react-native-sdk': {
platforms: { ios: null },
},
},
};Step 2 — declare the slim subspec explicitly in ios/Podfile inside your app target:
target 'YourApp' do
config = use_native_modules!
use_react_native!(...)
pod 'veryai-react-native-sdk/Core',
:path => '../node_modules/@veryai/react-native-sdk'
endStep 3:
cd ios && pod installTo go back to bundled, delete the react-native.config.js entry and the pod 'veryai-react-native-sdk/Core' line, then pod install again.
Android — switching to slim
In android/app/build.gradle (the app module, not the project root), add:
android {
packaging {
jniLibs {
excludes += '**/libPalmAPISaas.so'
}
}
}This filters the bundled .so out at packaging time. The runtime falls through to CDN download.
Verifying which mode you're in
iOS — after pod install:
grep -c "VerySDK_BundledModel\|packed_data" ios/Pods/Pods.xcodeproj/project.pbxproj
# > 0 → bundled, 0 → slimAndroid — after a build:
unzip -l android/app/build/outputs/apk/debug/app-debug.apk | grep libPalmAPISaas
# matches → bundled, no output → slimCDN endpoints (slim mode)
The SDK tries the primary CDN first and falls back automatically. Allowlist both hostnames if your network has egress restrictions.
| Asset | Primary | Backup |
| -------------------------------- | ------------------------------------------------ | ------------------------------------------------------- |
| Android libPalmAPISaas.so (per ABI) | assets.very.org/sdk/data/<abi>/... | r2.assets.very.org/sdk/v1/<abi>/... |
| iOS packed_data.bin | assets.very.org/sdk/data/packed_data.bin | r2.assets.very.org/sdk/v1/packed_data.bin |
Create an SDK App and Key
Create a Palm Verification SDK app in the Developer Portal and copy its SDK key. Native SDK integrations do not use an OAuth client ID, client secret, or authorization-code exchange.
Enroll a New User
Pass undefined for userId to register a new user. The SDK opens a consent screen, collects the user's email, then guides them through a palm scan.
import { VerySDK } from "@veryai/react-native-sdk";
const supported = await VerySDK.isSupported();
if (!supported) {
console.warn("Device not supported");
return;
}
const result = await VerySDK.authenticate({
sdkKey: "your_sdk_key",
userId: undefined, // undefined = new enrollment
themeMode: "dark",
presentationStyle: "fullScreen",
clientReferenceId: "gate-withdrawal-42", // your transaction/session ID
});
if (result.isSuccess) {
console.log("User ID:", result.userId);
console.log("User status:", result.userStatus);
console.log("Signed token:", result.signedToken);
} else {
console.error("Error:", result.error, result.errorMessage);
}Verify an Existing User
Pass the user's ID from a previous enrollment to verify their identity.
const result = await VerySDK.authenticate({
sdkKey: "your_sdk_key",
userId: "vu-1ed0a927-...", // from previous enrollment
themeMode: "dark",
});
if (result.isSuccess) {
console.log("Verified user:", result.userId);
console.log("User status:", result.userStatus);
console.log("Signed token:", result.signedToken);
}Verify the Signed Result (Backend)
Send signedToken to your backend and verify it with VeryAI's JWKS endpoint as
described in the Native SDK integration guide.
The Native SDK does not use OAuth authorization-code exchange.
Store userId after enrollment and pass it back for future verification.
Treat userStatus: "pending" as a successful submission awaiting review; use
webhooks for the later approved or rejected transition. A review that has
already landed on rejected by the time the flow ends comes back as a failure
instead — isSuccess: false, error: "5010", userStatus: "rejected" — with
userId and signedToken still set.
Configuration Reference
VeryConfig
| Parameter | Type | Default | Description |
| ------------------- | ------- | -------------- | ------------------------------------------------------------------ |
| sdkKey | String | required | Your SDK API key |
| userId | String? | undefined | Undefined for enrollment, user ID for verification |
| language | String? | device | Locale code (e.g. "en", "es", "ja"). 38 languages supported. |
| themeMode | String | "dark" | "dark" or "light" |
| presentationStyle | String | "fullScreen" | "fullScreen" or "bottomSheet". "bottomSheet" is deprecated — it maps to the iOS page sheet / Android bottom sheet, both of which will be removed in a future major. |
| livenessMode | String? | "touch" | Deprecated — ignored. The SDK picks the mode per device: the connect-the-dots touch points, except on constrained Android setups (3 GB of RAM or less, or a 32-bit process), which get the hand-gesture prompts. Still accepted so existing apps keep type-checking. |
| debugLogging | boolean | false | Verbose API request/response logging to the native console. |
| clientReferenceId | String? | undefined | Your session or transaction ID, included in related review webhook events. Server-enforced 255-character limit — a longer value fails the session, the SDK does not truncate. |
| customStrings | object? | undefined | Overrides for the scan status copy, keyed by VeryCustomString. See Custom strings. |
Custom strings
The scan page's status copy can be replaced per call. A non-blank value wins over the SDK's built-in localized string; blank values and unrecognized keys are ignored and fall back to it.
import { VerySDK, VeryCustomString } from "@veryai/react-native-sdk";
await VerySDK.authenticate({
sdkKey: "your_sdk_key",
customStrings: {
[VeryCustomString.SHOW_YOUR_HAND]: "Hold up your palm",
[VeryCustomString.CONNECT_DOTS]: "Trace the dots",
},
});| Key | Default text | Where it shows |
| --- | ------------ | -------------- |
| VeryCustomString.SHOW_YOUR_HAND | Show your hand | Default scan-page status |
| VeryCustomString.SHOW_YOUR_FIRST_HAND | Show your first hand | Scan-page status during first-time enrollment |
| VeryCustomString.CONNECT_DOTS | Connect the dots | Connect-the-dots gesture prompt |
The SDK renders your value verbatim, so pass text you have already localized —
language does not translate it. Plain strings work too; the constants exist so
a typo becomes a compile error rather than a silently ignored key.
Liveness-only flow not wrapped yet. The native SDKs also ship a standalone liveness check (
VeryAILiveness/VeryLivenessConfig) withshowError/showSuccessoptions. The React Native wrapper currently exposes only theauthenticate(palm-auth) flow; surfacing the liveness flow is tracked separately.
VeryResult
| Property | Type | Description |
| -------------- | ------- | ------------------------------------------------------------- |
| isSuccess | boolean | Whether authentication completed successfully |
| code | string | SDK result code — "success" on completion. Not an OAuth authorization code and not meant for backend exchange; verify signedToken instead. |
| userId | string | The user's VeryAI ID |
| signedToken | string? | Signed JWT token (when available) |
| userStatus | string? | approved, pending, rejected, restricted, or unknown. A status this wrapper version does not know is reported as unknown. |
| error | string? | Error code string |
| errorMessage | string? | Human-readable error message |
Requirements
- React Native 0.73+ (declared in
peerDependencies, so npm warns rather than failing at build time) - iOS 13.0+ / Android 6 (API 23)+
- Physical device (no simulator)
- Camera permission
Architecture
Both React Native architectures are supported — the package ships a TurboModule for the New Architecture and a legacy bridge module for the old one, and picks per build.
Note that the choice is no longer yours on recent React Native: 0.82 removed the
option to disable the New Architecture (newArchEnabled: false is ignored), and
0.83 deleted the legacy architecture outright. The legacy path in this package
therefore only matters if you are on React Native below 0.82.
Verified combinations
CI builds these two on every change to the SDK or the examples:
| Example | React Native | Architecture |
| ------- | ------------ | ------------ |
| examples/ReactNativeExample | 0.73.11 | legacy |
| examples/ExpoNewArchExample (Expo SDK 54) | 0.81.5 | new |
Anything outside those two points is expected to work but is not covered by a build. If you hit a problem on a version we do not test, please report it with your React Native version.
Error Codes
The full list lives in the top-level README. One slim-mode-specific code worth calling out:
| Code | Name | Meaning |
|------|-------------------------------|------------------------------------------------------------------------|
| 6106 | NATIVE_LIB_DOWNLOAD_FAILED | Slim-mode partner: the SDK couldn't fetch the palm asset from CDN. Check internet, CDN allowlist, or revert to bundled mode. |
Use VerySDK.isSupported() to check device compatibility at runtime before showing the verification UI.
Prefetching the palm asset (slim mode)
In slim mode the palm asset is downloaded from CDN on the first scan, which can
add a 5–15s wait. Call VerySDK.prefetch() early — e.g. at app launch — to warm
that download in the background so the first scan is instant. It's
fire-and-forget, safe to call repeatedly, and a no-op in bundled mode or once the
asset is cached. (isSupported() already triggers the same warm-up as a side
effect, so an explicit prefetch() is only needed when you want to start the
download without the support check.)
import { VerySDK } from "@veryai/react-native-sdk";
// e.g. in your app's startup
VerySDK.prefetch();