Skip to content

Expo / React Native SDK

Two ways to put the flow on a phone, sharing one implementation:

  • Embed the SDK (@baqshi/kyc-expo, this page) in your own Expo app — users never leave your product.
  • The BAQSHI Verify app (apps/verify in the repo) — the standalone store app for users arriving via desktop QR or a link. It’s built on this same SDK; if your product doesn’t want to embed capture, just show the handoff_url and either the installed app or the hosted web page takes over.

@baqshi/kyc-expo is a small typed client for Expo apps. It holds only a handoff token — never an API key — so it can ship safely inside any app bundle.

Terminal window
npm install @baqshi/kyc-expo expo-camera

Three common routes:

  1. Your backend response — app asks your backend to start KYC; backend calls POST /v1/verifications and returns the handoff_token.
  2. Deep link — your backend sends the handoff_url by email/SMS; your app is registered for the link.
  3. QR scan — desktop shows the QR; your app scans it:
import { BaqshiKyc } from "@baqshi/kyc-expo";
const token = BaqshiKyc.tokenFromUrl(scannedUrl); // null if not a handoff URL
import { BaqshiKyc } from "@baqshi/kyc-expo";
const kyc = new BaqshiKyc({ baseUrl: "https://kyc-server-production.up.railway.app" });
const session = kyc.session(token);
// 1. Where are we? (safe to call any time — resume after app restarts)
const { next_step } = await session.status(); // "upload_document" | "upload_selfie" | …
// 2. Document photo (expo-camera or expo-image-picker uri)
await session.uploadDocument(docPhoto.uri, "front");
// 3. Liveness — the SDK fetches the pose challenge and drives it;
// you supply the camera and the UI prompt:
await session.runLivenessFlow(async (pose, index, prompt) => {
setPromptText(prompt); // "Turn your head to your left"
const photo = await cameraRef.current.takePictureAsync({ quality: 0.9 });
return photo.uri;
});
// 4. Wait for the verdict (polls; webhook hits your backend in parallel)
const result = await session.waitForDecision({ timeoutMs: 120_000 });
switch (result.status) {
case "approved": /* celebrate */ break;
case "rejected": /* show retry/support path */ break;
case "manual_review": /* "we'll notify you shortly" */ break;
}

Capture quality guidance (put this in your UI)

Section titled “Capture quality guidance (put this in your UI)”

The single biggest driver of manual-review rates is capture quality. Coach users:

  • Document: flat surface, all four corners in frame, no glare from lights, no fingers over text. The MRZ (the two lines of <<< text on passports) must be fully readable.
  • Selfie: face the camera at eye level, even lighting, no hat/sunglasses, one person in frame. For the turn steps, a clear turn (~30–45°) — not a tilt.

All failures throw BaqshiKycError with status and detail:

statusTypical causeHandle by
401Token expired (30 min) or revokedRestart the flow via your backend
409Step out of order / duplicate frameCall status() and resume from next_step
413 / 415 / 422Bad image (too large, wrong type, too small)Re-capture
429Rate limitedBack off; the limiter resets within a minute