@drukverify/ekyc-sdk-js
Universal JavaScript SDK for eKYC — face verification, document OCR, liveness detection, and fraud analysis. Works in both browser and Node.js.
Zero runtime dependencies — uses native fetch and FormData.
Installation
npm install @drukverify/ekyc-sdk-js
Quick Start
Node.js (server-side, secret key)
import { EkycClient } from '@drukverify/ekyc-sdk-js';
import { readFileSync } from 'node:fs';
const client = new EkycClient({
baseUrl: 'https://api.drukverify.com',
apiKey: process.env.DV_SECRET_KEY, // dv_sk_live_… — server-side only
});
const image = readFileSync('./cid-front.jpg');
const ocr = await client.ocr.scanDocument(image, { type: 'cid' });
console.log(ocr.fields.full_name, ocr.fields.date_of_birth);
Browser (session token)
Never embed a secret key in browser code. Your backend mints a short-lived
session token via POST /v1/sessions (with the secret key) and hands it to
the page:
import { EkycClient } from '@drukverify/ekyc-sdk-js';
const { token } = await fetch('/your-backend/session').then(r => r.json());
const client = new EkycClient({
baseUrl: 'https://api.drukverify.com',
apiKey: token, // dv_tok_…
});
const file = document.querySelector('input[type=file]').files[0];
const fraud = await client.fraud.detect(file, { documentType: 'cid' });
console.log(fraud.is_fraudulent, fraud.overall_score);
Configuration
const client = new EkycClient({
baseUrl: 'https://api.drukverify.com', // required, no trailing slash
apiKey: token, // dv_tok_… (browser) or dv_sk_… (server)
version: '2026-05-12', // Drukverify-Version header (default shown)
timeout: 30000, // ms
headers: {}, // extra headers
});
API Reference
Document OCR
const r = await client.ocr.scanDocument(image, {
backImage, // optional back side
type: 'cid', // optional hint: 'cid' | 'passport' (others auto-detect)
customerRef: 'u-123', // optional
});
// r.document_type, r.fields (per-type shape), r.ocr_id
await client.ocr.get(id); // GET /v1/ocr/:id
await client.ocr.list(); // GET /v1/ocr
await client.ocr.delete(id); // DELETE /v1/ocr/:id
Fraud Detection
const r = await client.fraud.detect(image, { documentType: 'cid' });
// r.is_fraudulent, r.overall_score, r.signals.{fake_document,photo_substitution,tampering,copy_or_screen}
await client.fraud.get(fcId); // GET /v1/fraud/checks/:id
Liveness
// Passive (1 frame)
const r = await client.liveness.check(selfie);
// Burst (3–5 frames)
const r2 = await client.liveness.check([f1, f2, f3], { mode: 'burst' });
// Challenge (server-chosen action)
const ch = await client.liveness.createChallenge(); // { action, challenge_token, expires_at }
// …show ch.action to the user, capture ≥2 frames while they perform it…
const r3 = await client.liveness.check(frames, {
mode: 'challenge',
challengeToken: ch.challenge_token, // one-shot, ~120s
});
// r3.is_live, r3.score, r3.reason
await client.liveness.get(lcId);
Face Verification
// 1:1 — document portrait vs selfie
const r = await client.verify(documentImage, selfieImage);
// r.match, r.similarity
// 1:N gallery
await client.verification.registerFace(image, 'customer-ref');
await client.verification.searchFace(image, { limit: 5, minSimilarity: 0.4 });
await client.verification.detectDuplicate(image);
await client.verification.listRegistrations();
await client.verification.deleteRegistration(id);
Branding & Theming
The SDK imposes no UI. All screens, flows, layouts, fonts, and result rendering are your application's own HTML/CSS — the SDK is an API client plus two optional camera components. Everything below concerns only those two components.
Theme the built-in capture components
const capture = new FaceCapture(container, {
theme: {
accentColor: '#e30613', // guide outline + button (your brand color)
buttonTextColor: '#fff',
maskColor: 'rgba(10,20,40,.6)', // dimmed area around the guide
guideLineWidth: 4,
},
labels: { captureButton: 'Take selfie' }, // any language, e.g. Dzongkha
});
Full CSS takeover
Set buttonClassName (and countdownClassName for FaceCapture) and the SDK
applies no inline styles at all — the elements are yours to style:
new DocumentCapture(container, {
theme: { buttonClassName: 'bank-btn bank-btn--capture' },
labels: { captureButton: 'Scan document' },
});
Headless mode
Render your own controls entirely — the component only manages the camera, guide overlay, and best-frame selection:
const capture = new FaceCapture(container, { showCaptureButton: false });
capture.onCapture(handleBlob);
await capture.start();
// your own button:
myButton.onclick = () => capture.captureWithCountdown();
And of course you can skip the capture components completely: every API method
accepts any Blob/File, so a fully custom getUserMedia UI works unchanged.
Image Input Types
All image parameters accept any of the following:
| Type | Example |
|---|---|
File |
From <input type="file"> |
Blob |
From canvas, camera, etc. |
Buffer |
Node.js fs.readFileSync() |
Uint8Array |
Raw bytes |
ArrayBuffer |
From fetch().arrayBuffer() |
string (base64) |
"/9j/4AAQ..." |
string (data URL) |
"data:image/jpeg;base64,..." |
Camera Capture (Browser)
The SDK includes vanilla JS camera capture components — no React/Vue required.
import { DocumentCapture, FaceCapture } from '@drukverify/ekyc-sdk-js/capture';
// Document capture with card-shaped guide
const docCapture = new DocumentCapture(containerElement, {
facingMode: 'environment', // back camera
guideColor: '#00ff88',
});
docCapture.onCapture((blob) => {
// Best-quality frame as JPEG Blob
client.ocr.scanDocument(blob);
});
await docCapture.start();
// Face capture with oval guide + countdown
const faceCapture = new FaceCapture(containerElement, {
facingMode: 'user', // front camera
countdown: 3, // 3-second countdown before capture
});
faceCapture.onCapture((blob) => {
client.liveness.check(blob);
});
await faceCapture.start();
// Clean up
docCapture.destroy();
faceCapture.destroy();
Capture Options
DocumentCapture:
| Option | Type | Default | Description |
|---|---|---|---|
facingMode |
'user' | 'environment' |
'environment' |
Camera selection |
width |
number |
1280 |
Preferred video width |
height |
number |
720 |
Preferred video height |
guideColor |
string |
'#00ff88' |
Overlay guide color |
bestFrameCount |
number |
5 |
Frames to evaluate for best quality |
FaceCapture:
| Option | Type | Default | Description |
|---|---|---|---|
facingMode |
'user' | 'environment' |
'user' |
Camera selection |
width |
number |
640 |
Preferred video width |
height |
number |
480 |
Preferred video height |
guideColor |
string |
'#00ff88' |
Overlay guide color |
bestFrameCount |
number |
5 |
Frames to evaluate for best quality |
countdown |
number |
3 |
Seconds before auto-capture (0 = manual) |
Image Utilities
import { resizeImage, toBlob } from '@drukverify/ekyc-sdk-js';
// Resize image (browser only — uses canvas)
const resized = await resizeImage(file, 1024, 768, 0.85, 'image/jpeg');
// Convert any input to Blob
const { blob, filename } = await toBlob(base64String);
Error Handling
import { EkycError } from '@drukverify/ekyc-sdk-js';
try {
await client.verify(doc, selfie);
} catch (err) {
if (err instanceof EkycError) {
console.error(err.message); // Human-readable error
console.error(err.status); // HTTP status code (0 for timeout)
console.error(err.body); // Raw response body
}
}
TypeScript Types
All response types are exported:
import type {
// Verification
VerifyFaceResult, DetectFacesResult, Face,
RegisterFaceResult, SearchFaceResult, FaceMatch,
// OCR
DocumentOcrResult, PassportOcrResult, MrzData,
// Liveness
LivenessResult, DeepfakeResult,
// Fraud
FraudAnalysisResult, FakeDocumentResult,
PhotoSubstitutionResult, TamperingResult,
CopyScreenResult, SecurityFeaturesResult,
// Batch
BatchSubmitResult, BatchStatusResult, BatchResultsResponse,
// Config
EkycConfig,
// Image
ImageInput,
} from '@drukverify/ekyc-sdk-js';
License
MIT