v3.0.0
Identity verification as an API for any platform that onboards people: lending, fintech, HR, healthcare, marketplaces and gaming. 13-stage decision pipeline with AWS Textract OCR, risk scoring, fraud detection, face embedding deduplication, and strict response contracts.
v3.0 — 13-stage pipeline, risk scoring layer, face embedding dedup, AWS Textract OCR, idempotent endpoints, rate limiting.
Passport + MRZ match + NFC chip verified + liveness + face match
Passport + MRZ match + liveness + face match (no NFC)
Non-passport basic biometric verification
Base URL
/apiTwo authentication methods:
For operators creating KYC sessions
X-API-Key: pk_live_...For admin/operator dashboard access
Authorization: Bearer eyJ...Create a session
POST /api/kyc/start
Headers: X-API-Key: pk_live_your_key
Body: { "externalUserId": "user-123" }Redirect user to widget
https://your-domain.com/kyc?session={sessionToken}Receive webhook or poll
// Webhook: POST to your configured webhookUrl
// Or poll: GET /api/kyc/session/{sessionToken}Called by the KYC widget. Authenticated via sessionToken.
Passport-specific verification endpoints.
NFC is an optional enhancement — it never blocks verification. If NFC is unavailable, verification continues at PASSPORT_ENHANCED_VERIFIED level. NFC data inconsistency after a successful chip read WILL trigger a failure.
Computes a composite risk score (0-100) from weighted analysis of all verification signals. Integrated as Stage 11 in the decision pipeline.
0 – 29→ PASS (auto-approved)
30 – 60→ MANUAL_REVIEW (human review required)
61 – 100→ FAIL (auto-rejected)
// Risk scoring in decision/finalize response:
{
"riskScore": 18,
"riskLevel": "LOW",
"evaluation": {
"riskScore": 18,
"riskLevel": "LOW",
"stages": [{
"name": "risk_scoring",
"passed": true,
"details": {
"riskScore": 18,
"riskLevel": "LOW",
"breakdown": {
"ocrConfidence": { "score": 0.9, "raw": 0.94, "details": "..." },
"completeness": { "score": 0.0, "raw": 1.0, "details": "..." },
"faceMatch": { "score": 1.6, "raw": 92.0, "details": "..." },
"liveness": { "score": 0.4, "raw": 97.5, "details": "..." },
"fraud": { "score": 3.5, "raw": 10, "details": "..." }
}
}
}]
}
}Uses AWS Rekognition Collections to detect duplicate faces across sessions. Automatically triggered on selfie upload.
• faceId — AWS Rekognition face identifier
• similarity — Match score (0-100%)
• matchedSessionId — Previous session with matching face
• collectionId — Rekognition collection used
• rekognition:CreateCollection
• rekognition:IndexFaces
• rekognition:SearchFacesByImage
• rekognition:DescribeCollection
Non-blocking: gracefully degrades if permissions missing.
• /kyc/start — Rate limited (burst protection)
• Returns 429 Too Many Requests when exceeded
• Per-IP and per-API-key tracking
• /decision/finalize — Returns cached decision
• /ocr/run — Returns cached OCR result
• /face-match/run — Returns cached match
• Response includes idempotent: true
All API responses include x-request-id header for tracing. Optionally send your own.
// Request header (optional):
X-Request-Id: your-correlation-id
// Response header (always present):
X-Request-Id: abc123-uuidAll admin endpoints require Authorization: Bearer <token>. Session list/detail now includes document type, MRZ, NFC, verification level.
Signed webhook on PASS or FAIL. Includes verificationLevel, riskScore, and riskLevel.
POST {operator.webhookUrl}
X-Webhook-Signature: sha256=a1b2c3...
{
"event": "kyc.completed",
"data": {
"sessionId": "...",
"externalUserId": "user-12345",
"status": "PASS",
"verificationLevel": "PASSPORT_ENHANCED_VERIFIED",
"riskScore": 15,
"riskLevel": "LOW",
"decision": {
"outcome": "PASS",
"reasonCode": "PASS_AUTO"
}
}
}const crypto = require('crypto')
const expected = 'sha256=' + crypto
.createHmac('sha256', webhookSecret)
.update(JSON.stringify(payload)).digest('hex')
const valid = crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))STARTED
→ DOCUMENTS_UPLOADED
→ OCR_COMPLETE
→ MRZ_PARSED (passport only)
→ NFC_COMPLETE (if NFC attempted)
→ LIVENESS_CREATED → LIVENESS_COMPLETE
→ FACE_MATCH_COMPLETE
→ PASS ✅ | FAIL ❌ | MANUAL_REVIEW ⏳Each stage evaluates independently with its own reason code. First FAIL stops the pipeline. First MANUAL_REVIEW flags for human review. Risk scoring provides a composite 0-100 score.
ID front + selfie present and usable
Reason codes: FAIL_IMAGE_UNREADABLE, FAIL_NO_SELFIE
OCR succeeded via AWS Textract
Reason codes: FAIL_OCR, FAIL_IMAGE_UNREADABLE
Required fields present per document type
Reason codes: FAIL_MISSING_REQUIRED_FIELDS, MANUAL_REVIEW_LOW_CONFIDENCE
OCR confidence ≥ 70%, field completeness scoring
Reason codes: MANUAL_REVIEW_LOW_CONFIDENCE, FAIL_OCR
MRZ readable (passport only)
Reason codes: MANUAL_REVIEW_MRZ_UNREADABLE
OCR matches MRZ data
Reason codes: FAIL_OCR_MRZ_MISMATCH, MANUAL_REVIEW_PASSPORT
Not expired, age ≥ minimum age, valid format
Reason codes: FAIL_PASSPORT_EXPIRED, FAIL_UNDERAGE, FAIL_DOCUMENT_EXPIRED
Confidence ≥ 80%
Reason codes: FAIL_LIVENESS
≥80 pass, 70-79 review, <70 fail
Reason codes: FAIL_FACE_MATCH, MANUAL_REVIEW_BORDERLINE_FACE
Document integrity analysis — tampering score
Reason codes: FAIL_IMAGE_TAMPERED, MANUAL_REVIEW_FRAUD_SUSPECTED
Composite 0-100 risk score (LOW/MEDIUM/HIGH)
Reason codes: FAIL_HIGH_RISK, MANUAL_REVIEW_MEDIUM_RISK
Identity + face embedding duplicate detection
Reason codes: MANUAL_REVIEW_DUPLICATE_IDENTITY, MANUAL_REVIEW_DUPLICATE_FACE
Aggregate result from all stages
Reason codes: PASS_AUTO, FAIL_*, MANUAL_REVIEW_*
PASSPORT_NFC_VERIFIEDPASSPORT_ENHANCED_VERIFIEDBASIC_VERIFIED| Code | Description |
|---|---|
PASS_AUTO | All checks passed automatically |
PASS_NFC_VERIFIED | Passed with NFC chip verification |
PASS_REVIEWED | Approved by manual reviewer |
FAIL_OCR | OCR extraction failed |
FAIL_LIVENESS | Liveness check failed |
FAIL_FACE_MATCH | Face similarity < 70% |
FAIL_PASSPORT_EXPIRED | Passport expired |
FAIL_DOCUMENT_EXPIRED | Non-passport document expired |
FAIL_UNDERAGE | Below the configured minimum age |
FAIL_OCR_MRZ_MISMATCH | Strong mismatch between OCR and MRZ |
FAIL_IMAGE_UNREADABLE | No usable document image |
FAIL_NO_SELFIE | No selfie uploaded |
FAIL_NO_FACE_DETECTED | No face in document or selfie |
FAIL_NFC_DATA_INCONSISTENT | NFC chip data inconsistent |
FAIL_PASSPORT_VALIDATION | Passport validation failed |
FAIL_MISSING_REQUIRED_FIELDS | Required fields not extracted by OCR |
FAIL_IMAGE_TAMPERED | Document tampering detected (fraud) |
FAIL_HIGH_RISK | Risk score > 60 (HIGH) — composite risk too high |
FAIL_REVIEWED | Rejected by reviewer |
MANUAL_OCR_PARTIAL | OCR partially successful |
MANUAL_REVIEW_MRZ_UNREADABLE | MRZ not readable |
MANUAL_REVIEW_POOR_IMAGE | Borderline image quality |
MANUAL_REVIEW_BORDERLINE_FACE | Face match 70-79% |
MANUAL_REVIEW_PASSPORT | Partial OCR/MRZ mismatch |
MANUAL_REVIEW_LOW_CONFIDENCE | Low OCR confidence or completeness |
MANUAL_REVIEW_FRAUD_SUSPECTED | Fraud signals detected in document |
MANUAL_REVIEW_MEDIUM_RISK | Risk score 30-60 (MEDIUM) — needs review |
MANUAL_REVIEW_DUPLICATE_IDENTITY | Duplicate name+DOB or document number |
MANUAL_REVIEW_DUPLICATE_FACE | Duplicate face found via embedding match |
RETRY_REQUESTED | Reviewer requested retry |
Passport + OCR/MRZ match + NFC chip consistent + liveness ≥ 80% + face match ≥ 80%
Passport + OCR/MRZ match + (no NFC or NFC skipped) + liveness ≥ 80% + face match ≥ 80%
Non-passport document + liveness + face match (Phase 1 basic biometric only)
{ "error": "Description of the error" }| Code | Description |
|---|---|
200 | Success |
201 | Created |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
404 | Not Found |
410 | Session Expired |
429 | Rate Limit Exceeded |
500 | Server Error |
eKYC Middleware Platform v3.0.0
13-stage decision pipeline • Risk scoring • Face embedding dedup • AWS Textract OCR