Self-hosted identity verification, designed around New Zealand's standards โ AI document inspection, live face checks, encrypted storage that can go offline, and signed attestations so your products never need to touch raw ID data.
Live service NZ-first design API + Expo SDK
๐ Full documentation: kyc-docs-production.up.railway.app โ integration guides, security architecture, threat model, fraud-signal catalogue, incident runbooks, NZ compliance mapping, and the API reference.
When one of our products needs to know that a user is a real, living person who matches a genuine government ID, it sends them here. The user photographs their ID, completes a short live face challenge (turn your head when asked โ a photo or video replay can't follow instructions), and our pipeline decides: approved, rejected, or manual review. The requesting product gets back a signed "yes, verified" attestation โ never the ID images themselves.
Plain-language summary of how the system is designed against the three NZ frameworks that matter for identity verification. This is a design statement, not a certification.
| Framework | What it asks | How BAQSHI-KYC answers |
|---|---|---|
| AML/CFT Act โ Identity Verification Code of Practice | Verify name + date of birth from an approved photo ID, and that the person presenting it is its holder. | Document authenticity checks (MRZ maths + AI tamper inspection), biometric face match to the document portrait, liveness challenge, and a complete tamper-evident audit trail of every step. |
| Biometric Processing Privacy Code 2025 | Process biometrics only with safeguards: necessity, transparency, retention limits, no repurposing. | Biometrics processed locally only, used solely for the match/liveness decision, never used to train anything, deleted with the verification. Users see what is collected at capture time. Retention: 30 days hot, then encrypted offline archive; erasure on request (see below). |
| Privacy Act 2020 (IPP 5, 9, 13) | Reasonable security safeguards, keep no longer than necessary, honour deletion. | Per-record envelope encryption (AES-256-GCM), PII kept out of the operational database, hash-chained audit log, and cryptographic erasure: destroying a record's data key makes every copy โ including offline archives โ permanently unreadable. |
Verification artifacts (ID photos, selfie frames, extraction data) live encrypted in the cloud vault for 30 days โ long enough to resolve disputes and manual reviews. After that they are bundled, hash-manifested, and moved to offline storage. The operational database keeps only the decision, minimal identity fields, and hashes. A deletion request destroys the encryption keys: the data is gone everywhere at once, including on the offline media.
verified,
over_18, โฆ). Verify against our
public key.Three calls from your backend, one flow on the user's device.
curl -X POST $BASE/v1/verifications \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"external_user_ref": "user-123", "webhook_url": "https://yourapp.com/kyc-hook"}'
# โ { "verification_id": "ver_โฆ", "handoff_token": "โฆ", "handoff_url": "https://โฆ/v/โฆ" }
Show handoff_url as a QR code (desktop) or open it / pass the token to
the Expo SDK (mobile). The token expires in 30 minutes.
Via the hosted page at /v/{token} (no app needed), or in your Expo app:
import { BaqshiKyc } from "@baqshi/kyc-expo";
const session = new BaqshiKyc({ baseUrl: BASE }).session(token);
await session.uploadDocument(docPhoto.uri, "front");
await session.runLivenessFlow(async (pose, i, prompt) => {
showPrompt(prompt); // "Turn your head to your left"
return (await camera.takePictureAsync()).uri;
});
const result = await session.waitForDecision();
Your webhook receives {type, verification_id, status}. Verify the
signature before trusting it:
# signature = HMAC_SHA256(key, f"{timestamp}.{raw_body}")
expected = hmac.new(KEY, f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
assert hmac.compare_digest(expected, request.headers["X-Baqshi-Signature"])
assert abs(time.time() - int(ts)) < 300 # reject stale deliveries
Then fetch details or request an attestation:
GET /v1/verifications/{id} # status + minimal extracted fields
POST /v1/attestations/{id} # {"claims": ["verified", "over_18"]}
GET /v1/attestations/public-key # PEM for signature verification
DELETE /v1/verifications/{id}/data # cryptographic erasure (privacy requests)
GET /v1/review/queue ยท POST /v1/review/{id} # manual review operations
Full interactive API reference: /docs (OpenAPI).
python -m app.archival run on a schedule moves
>30-day artifacts to the offline archive location.429.