Risk · Duplicate detection
One person, one account, as far as your verifications can tell.
Every session stores a hashed fingerprint of its document number and a face embedding. At submit, both are compared with your app's other verifications, so the same document or the same face behind a new name is surfaced to a reviewer.
- signals: document fingerprint and face embedding
- 2
- default face similarity threshold
- 0.55
- comparison scope
- app
What it checks
Duplicate detection, check by check.
Each rule below is what the engine actually runs. The result is written as a duplicate check with its score and warnings.
Same document
Document numbers are stored as HMAC-SHA256 fingerprints under a key derived from the service's master key and salted with your organisation, so a match is found without keeping the number in clear, and a fingerprint cannot be brute-forced back to the number without the key.
Same face
The selfie embedding is compared with those of approved, in-review, processing and declined sessions in the app; a similarity at or above the face threshold counts.
Review, not decline
A duplicate sends the session to review with
duplicate_foundand the list of matching sessions.
Try it
Hash a document number the way the engine does.
Two sessions, one passport. The fingerprint is a keyed HMAC salted with your organisation, so the same document matches inside your organisation and nowhere else, and cannot be reversed without the service's master key. Add a face similarity and see when a reviewer is called.
- The rules are ported line for line from the Rust engine, and the page names the file.
- Everything runs locally in this tab. No request is made while you type.
- Reset puts the example back; nothing is saved.
Duplicate detection: fingerprints and faces
The number is never compared in clear. Each session stores hmac_sha256(key, org ␟ kind ␟ country:type:number), under a key derived from the service’s master key, so the same document only meets itself inside the same organisation and nobody without the key can reverse it.
Face
Cosine of the two SFace embeddings.
Default 0.55, deliberately stricter than face match.
Passed: no duplicate
Try changing the organisation of the new session: the same passport no longer matches. Inside one organisation, scope decides which sessions are searched: one app by default, which is how sandbox and live stay apart, or every app with "org".
Logic ported from backend/crates/kyc-api/src/engine/mod.rs · submit.rs. Nothing you type leaves this page.
How it works
What happens, in order.
- 1
Fingerprint
Document steps write a fingerprint; liveness writes a selfie embedding.
- 2
Compare
At submit, fingerprints are joined against other sessions in the app, and the face is compared with stored embeddings.
- 3
Explain
The check lists how many fingerprints and faces were compared, and which sessions matched.
Configuration
The workflow keys and their defaults.
"duplicate": { "enabled": true, "face_threshold": 0.55, "scope": "app" }| Key | Default | Meaning |
|---|---|---|
| face_threshold | 0.55 | Cosine at or above which two faces are the same person for this purpose. |
| scope | app | app compares within one app (sandbox and live stay apart); org compares across your organisation's apps. |
Reference
What is compared, and against what.
| Signal | Stored as | Matches when |
|---|---|---|
| Document number | HMAC-SHA256 of org, kind and country:type:number | The fingerprints are equal |
| Aadhaar number from card OCR | HMAC-SHA256 of org, kind and the number | The fingerprints are equal |
| PAN | HMAC-SHA256 of org, kind and the PAN | The fingerprints are equal |
| Document image | HMAC-SHA256 of org, kind and the front image's SHA-256 | The same file is uploaded again |
| Face | SFace embedding of the selfie (or portrait) | Cosine at or above face_threshold (0.55) |
Reasons and warnings
Exact codes, as they appear in the check's data and warnings, so you can branch on them.
| Code | Outcome | When |
|---|---|---|
| duplicate_found | review | A document number, PAN, identical image or face matches another approved, in-review, processing or declined session in scope. |
| no_identifiers | skipped | No fingerprint or face was captured, so nothing could be compared. |
API
Duplicates arrive with the decision.
Duplicate detection compares a session with its neighbours at submit. The check lists how much was compared and which sessions matched, by what.
Duplicates arrive with the decision.
# 1. Create a session on a workflow that has duplicate detection on
curl -X POST https://kycverify.me/api/v1/sessions \
-H "x-api-key: $KYC_API_KEY" \
-H "content-type: application/json" \
-d '{ "workflow_id": "wf_…", "vendor_data": "user-42" }'
# 2. After the session.status_updated webhook, read the decision
curl https://kycverify.me/api/v1/sessions/ses_…/decision \
-H "x-api-key: $KYC_API_KEY"decision.checks[] · duplicate
{
"kind": "duplicate",
"status": "review",
"score": 1,
"data": {
"scope": "app",
"face_threshold": 0.55,
"compared": { "fingerprints": 1, "faces": 214, "face_available": true },
"duplicates": [
{ "session_id": "ses_…", "matched_on": ["document_number", "face"], "face_similarity": 0.71 }
],
"duplicate_count": 1,
"reason": "duplicate_found"
},
"warnings": [
{ "code": "duplicate_found", "message": "This person or document matches 1 other verification(s)", "severity": "high" }
]
}Limits
What it does not do.
Stated up front, so you can decide what to pair it with.
- Comparison stays inside one app by default, or spans your organisation's apps with
scope: "org". It never crosses organisations, and only sessions whose data has not been purged by retention are compared. - Face comparison is a linear scan of the app's stored embeddings, sized for one app's verification history rather than a billion-face index.
FAQ
Duplicate detection: questions.
Is the data shared across customers?
No. Comparison never leaves the app: another customer's sessions, and even your own other apps, are never searched.
Try it in the sandbox today.
Every check is available from the first sign-up, with test keys and a default workflow. Talk to us when you are ready to verify real people.