React Native reference
Every public type in the FacePing React Native package: setup, enrolment, verification, the camera component and results.
Package: @faceping/react-native (npm, preview, under the preview tag). Starter app: faceping/react-native-starter. Android 7 (API 24) or later, on 64-bit phones (arm64-v8a) and x86_64 emulators. iOS 15.1 or later; Xcode 26 or later.
The package is a thin bridge over the native Android and iOS SDKs, built with Expo modules: it works in any Expo app with a development build (not Expo Go), and in bare React Native apps with Expo modules installed. New and old architecture. Tested with Expo SDK 57 and React Native 0.86.
Install#
Expo#
npx expo install @faceping/react-native@preview
npx expo install adds the config plugin to app.json. Give it options as below, then rebuild (npx expo prebuild, npx expo run:ios, npx expo run:android, or EAS Build):
{
"expo": {
"plugins": [
["@faceping/react-native", { "cameraPermission": "We use the front camera to check it's you." }]
]
}
}| Plugin option | Default | Notes |
|---|---|---|
cameraPermission |
A generic sentence | The iOS camera prompt, set as NSCameraUsageDescription. Without it, iOS stops the app when the camera starts. |
androidAbis |
true |
Builds Android for arm64-v8a and x86_64 only, the ABIs FacePing runs on. false keeps your own list. |
Bare React Native#
npx install-expo-modules@latest # once, if your app doesn't use Expo modules yet
npm install @faceping/react-native@preview
cd ios && pod install
Then do by hand what the plugin does:
- iOS: add
NSCameraUsageDescriptiontoInfo.plist(Xcode: the target's Info tab, Privacy - Camera Usage Description), and make sure the deployment target is 15.1 or later. - Android: in
android/gradle.properties, setreactNativeArchitectures=arm64-v8a,x86_64.
How the native SDKs get into your app#
- Android: the module depends on
ai.faceping:faceping-androidfrom Maven Central. Gradle fetches it like any other dependency, with theCAMERAandINTERNETpermissions. - iOS:
pod installdownloadsFacePing.xcframework.ziponce from the faceping-ios release, checks its SHA-256 against the checksum pinned in the package, and vendors it. Offline or behind a proxy, setFACEPING_IOS_ZIP=/path/to/FacePing.xcframework.zip(checked the same way) orFACEPING_IOS_URLto a mirror beforepod install. The simulator has no front camera: test the live check on an iPhone.
Setup#
import { FacePing } from '@faceping/react-native';
// Once, early. Resolves at once and loads the face models in the background.
await FacePing.init({ sandboxKey: 'fp_test_…' }); // sandbox: faces enrolled on the device
// await FacePing.init({ deviceToken: 'fpd_…' }); // production: faces synced from your check-in list
// Activates the sandbox key now, so a key problem shows up before the first person arrives.
await FacePing.sync();
FacePing is a single object you can call from anywhere in the app. Calling init again with the same options is fine (for example after a fast refresh); with different options it throws (call close() first). It resolves to { isSandbox, nativeSdkVersion }.
A key in the wrong format throws a setup error from init. A revoked key, or a first run with no connection, shows up on the first call that needs the key, so call sync() at start-up.
Options#
| Option | Type | Default | Notes |
|---|---|---|---|
sandboxKey |
string |
A fp_test_ key from Developers → Sandbox keys. |
|
deviceToken |
string |
Production. A fpd_ token for one check-in list. Set this or sandboxKey, not both. |
|
region |
'eu' |
'eu' |
Your account's region (sets the API address). |
apiBaseUrl |
string |
Overrides the region's address, for example for the UK or US region. | |
passiveLiveness |
boolean |
true |
Passive liveness on every check. Turn it off only if your app runs its own liveness check first. |
autoSyncInterval |
number \| null |
300 |
Production only, in seconds. null turns automatic sync off; call sync() yourself. |
FacePing#
| Call | Returns | Notes |
|---|---|---|
enrol(id, photo) |
EnrolResult |
Sandbox only. Production enrolment happens with consent on your server or through the FacePing widget. Replaces any face already stored under id. |
verifyLive(id, cameraRef) |
VerifyResult |
A live check on the <FaceCamera>: match, a head turn in a random direction, match again. Shows the prompts and sets the guide. About 10 seconds at most. |
verify(id, photo) |
VerifyResult |
One photo, passive liveness only. |
forget(id) |
boolean |
Sandbox: deletes the face now; false if there was none. Production: throws invalidOperation (remove the person through your server). |
forgetAll() |
Deletes every face on the device now. In production the next sync brings back the people still enrolled. | |
enrolledIds() |
string[] |
Who can be verified on this device, as of the last enrol, forget or sync. |
sync() |
SyncResult |
Production: downloads the latest faces for the device's check-in list; a revoked device wipes its faces. Sandbox: activates the key, or renews its licence when due. |
close() |
Stops automatic sync and frees the engine. Call init again to start over. |
Every call returns a promise.
Photos#
A photo is any of:
| From | Example |
|---|---|
The <FaceCamera> (takes a still from the live view) |
FacePing.enrol('me', cameraRef) |
A file URI (file://…, or on Android content://…, for example from a photo picker) |
FacePing.enrol('me', uri) or { uri } |
| Base64 JPEG or PNG (HEIC too on iOS) | { base64 }, or a data:image/jpeg;base64,… URI |
Photos are turned upright from their EXIF orientation and scaled down to 1280 pixels on the device before anything else happens.
FaceCamera#
The native front-camera view, with the oval face guide and the liveness prompts ("Turn your head left", "Look back at the camera"). The SDK runs the camera, so you don't need another camera library, and it asks for camera permission the first time the view appears. Give it a size.
const camera = useRef<FaceCameraRef>(null);
<FaceCamera ref={camera} style={{ height: 420 }} onCameraFailed={({ message }) => console.warn(message)} />
| Prop | Type | Notes |
|---|---|---|
showGuide |
boolean |
The face outline. Default true. |
onCameraFailed |
({ message }) => void |
The camera can't start: permission refused or no front camera. The message says which. |
style, testID |
As for any view. |
The ref (FaceCameraRef) has setGuideState('ready' | 'working' | 'match' | 'noMatch') and startCamera() (call it if your app gets camera permission another way). verifyLive sets the guide for you; set it yourself for other steps, for example 'working' while enrolling.
To translate or reword the prompts, use the native SDKs' strings: on Android the string resources faceping_prompt_look_at_camera, faceping_prompt_turn_left, faceping_prompt_turn_right and faceping_prompt_look_back; on iOS a FacePing.strings table with the keys faceping.prompt.lookAtCamera, faceping.prompt.turnLeft, faceping.prompt.turnRight and faceping.prompt.lookBack.
Backups#
The face store's encryption keys stay in the phone's Keychain or Keystore. On iOS, FacePing's folder is already excluded from backups. On Android, exclude files/faceping and the shared preferences faceping.keys from Auto Backup, or turn backup off (Expo: "android": { "allowBackup": false }).
Results#
type EnrolResult = {
outcome: 'enrolled' | 'noFace' | 'multipleFaces' | 'faceTooSmall' | 'notLive' | 'limitReached';
id: string;
isEnrolled: boolean;
};
type VerifyResult = {
outcome: 'match' | 'noMatch' | 'notEnrolled' | 'noFace' | 'livenessFailed'
| 'challengeFailed' | 'expired' | 'leaseExpired';
similarity: number; // cosine similarity; the match threshold is 0.363
livenessScore: number | null; // passive liveness of the deciding frame
elapsedMs: number; // how long the check took on the device
isSandbox: boolean;
isMatch: boolean;
};
type SyncResult = {
status: 'ok' | 'gone' | 'revoked' | 'unavailable';
enrolled: number;
leaseUntil: Date | null; // production: verifies offline until then
deleteAfter: Date | null;
};
The outcome types are exported as EnrolOutcome and VerifyOutcome. limitReached means the sandbox already holds 25 faces on this device: forget one first. leaseExpired (production) means the device hasn't synced for longer than its lease, 24 hours by default; it needs a connection before it verifies again.
Errors#
Set-up and device problems throw a FacePingError with a message ready to show and a code. isFacePingError(e, 'setup') checks for one.
| Code | When |
|---|---|
setup |
The key or token is missing, invalid or revoked, the sandbox key couldn't be activated (the first run needs a connection), or init hasn't been called. |
model |
The face models couldn't load on this device. Send people to your normal way in. |
invalidOperation |
Not allowed in this mode or state, for example enrolling on the device in production. |
invalidArgument |
A bad argument: an empty id, or a photo that isn't a readable image. |
storage |
The device's secure storage failed. |
camera |
The camera couldn't start or didn't deliver a picture, or the <FaceCamera> isn't on screen. |
unknown |
Anything else. The message says what. |
Everything else is a result, not an exception: a face check never throws because someone looked away.