Mobile

React Native

A hardened react-native-webview wrapper (RN 0.74+) with a component, a modal and a promise API.

View as Markdown

Version0.1.2

Install

Peer dependencies: react ≥ 18, react-native ≥ 0.74, react-native-webview ≥ 13, and optionally expo-screen-capture ≥ 6 (lets the SDK block screenshots and screen recording).

Terminal
npm install @okyc/react-native react-native-webview
npx expo install expo-screen-capture   # optional: screenshot blocking
cd ios && pod install

Permissions

iOS needs camera (and, for uploads, photo library) usage strings:

Info.plist
<key>NSCameraUsageDescription</key>
<string>The camera is used to capture your ID document and a selfie for identity verification.</string>

Android needs INTERNET and CAMERA:

AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<!-- Document + selfie capture: react-native-webview asks for the runtime permission when the page calls getUserMedia. -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />

When the page asks for the camera, react-native-webview requests the runtime permission. If the user refuses, the flow shows its own "camera blocked" state with an upload fallback where the workflow allows one. File inputs work on both platforms without extra code.

Start the flow

Create the session on your backend and pass its url to the app.

verify.tsx
export function makeConfig(sessionUrl: string): OkycConfig {
  return {
    sessionUrl, // the `url` returned by POST /v1/sessions
    locale: 'en', // optional; defaults to the session's language
    theme: 'system', // 'light' | 'dark' | 'system'
    allowedHosts: [], // extra hosts, only when self-hosting
    blockScreenshots: true, // needs expo-screen-capture
    autoCloseDelay: 2000, // ms the result screen stays visible after `complete`
  };
}
verify.tsx
export function App() {
  return (
    <OkycProvider>
      <VerifyButton />
    </OkycProvider>
  );
}

function VerifyButton() {
  const start = async () => {
    const sessionUrl = await fetchSessionUrlFromYourBackend();
    const result = await Okyc.start(makeConfig(sessionUrl), { onEvent });
    handleResult(result);
  };
  return <Button title="Verify identity" onPress={start} />;
}

Three ways to present it:

API
<OkycVerification config onComplete onCancel onError onEvent />Inline component; you own the screen.
<OkycModal visible config … />Full-screen modal with a toolbar; close and Android back ask "Exit verification?".
OkycProvider + useOkyc().start(config) or Okyc.start(config)Promise that resolves to an OkycResult. One verification at a time.

The modal form:

verify.tsx
export function VerifyScreen({ sessionUrl }: { sessionUrl: string }) {
  const [visible, setVisible] = useState(true);
  const done = (result: OkycResult) => {
    setVisible(false);
    handleResult(result);
  };
  return (
    <View style={{ flex: 1 }}>
      <Text>Verification</Text>
      <OkycModal
        visible={visible}
        config={makeConfig(sessionUrl)}
        onEvent={onEvent}
        onComplete={done}
        onCancel={done}
        onError={done}
      />
    </View>
  );
}

Options

OptionTypeDefaultDescription
sessionUrlstringrequiredThe url returned by POST /v1/sessions. Never embed a secret API key in an app.
localestringnullFlow language (en, hi); must be one of the workflow's languages. Sent as ?locale=.
themelight | dark | system"system"Colour scheme of the flow. Sent as ?theme=.
allowedHostsstring[][]Extra hosts the WebView may load (self-hosting).
blockScreenshotsbooleantrueAndroid: FLAG_SECURE. iOS: cover the flow in the app switcher and while the screen is recorded.
autoCloseDelayduration_ms2000 msHow long the result screen stays visible after complete before the SDK closes and returns completed. 0 or null = return the result immediately (no result screen).

Results and events

verify.tsx
function handleResult(result: OkycResult) {
  switch (result.type) {
    case 'completed':
      // UX only: confirm the decision on your backend (webhook or GET /v1/sessions/:id).
      console.log('finished', result.sessionId, result.status);
      break;
    case 'cancelled':
      console.log('user closed the flow');
      break;
    case 'failed':
      console.warn('verification failed', result.code, result.message);
      break;
  }
}
verify.tsx
function onEvent(event: OkycEvent) {
  // Payloads carry ids and statuses only — never personal data.
  track(`okyc_${event.type}`, { sessionId: event.sessionId, ...event.payload });
}

OkycResult is { type: 'completed', sessionId, status }, { type: 'cancelled', sessionId? } or { type: 'failed', sessionId?, code, message }. Event payloads are flat and PII-free (for example { step: 'pan' }).

Failure codes in React Native:

CodeMeaning
invalid_configMissing or malformed session URL or configuration.
load_failedThe flow page failed to load (HTTP error or blank page).
network_errorNo connection or DNS/TLS failure while loading.
navigation_blockedRepeated navigation to non-allowlisted hosts.
permission_deniedThe camera permission was permanently denied and the flow cannot continue.
flow_errorThe flow reported an unrecoverable error.
webview_unavailableThe WebView renderer crashed or is unavailable.

Theming and locale

theme: 'light' | 'dark' forces the flow's colour scheme before the page loads; system follows the device and your dashboard branding. The flow language comes from the session: pass locale when you create it (POST /v1/sessions); the applicant can also switch it in the flow.

Security defaults

  • Only the flow origin, DigiLocker and allowedHosts load; target=_blank https links open in the system browser; other hosts, http downgrades, file:, intent: and javascript: are blocked.
  • Bridge messages are accepted only from the session's own page (origin and session id; not DigiLocker or allowedHosts) and must be JSON with source: 'okyc', v: 1 and a known type; payloads are reduced to scalars and messages over 10 KB are dropped. blob:/data: URLs load only inside frames.
  • Incognito WebView without cache, shared or third-party cookies, file URLs or mixed content; gestures and link previews off; debugging only in __DEV__.
  • Screenshots and recording are blocked when expo-screen-capture is installed (isScreenshotBlockingAvailable() tells you).
  • The session URL is never logged or included in error messages.

Troubleshooting

SymptomFix
A verification is already in progressOnly one start() at a time; wait for the result.
Local http URL refusedHTTP is accepted only for localhost, 127.0.0.1, 10.0.2.2, [::1] and *.localhost.
Screenshots still possibleInstall expo-screen-capture (bare apps need expo-modules).

Going live

  • Create sessions with a sk_live_… key on your backend; nothing changes in the app.
  • Install expo-screen-capture so blockScreenshots takes effect.
  • Decide on the webhook, not on completed.

On this page