Skip to content

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.

kycverify · productduplicate
signals: document fingerprint and face embedding
2
default face similarity threshold
0.55
comparison scope
app
Fingerprints · embeddings

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_found and 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

Runs in your browser

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.

Earlier session
Kept on the identity
L8989****
Fingerprint (HMAC-SHA256, sample key)
…
New session
Kept on the identity
L8989****
Fingerprint (HMAC-SHA256, sample key)
…
fingerprints differ: no document match

Face

0.41

Cosine of the two SFace embeddings.

0.55

Default 0.55, deliberately stricter than face match.

threshold 0.55

Passed: no duplicate

Nothing matched among approved, in-review, processing and declined sessions that have not been purged.

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. 1

    Fingerprint

    Document steps write a fingerprint; liveness writes a selfie embedding.

  2. 2

    Compare

    At submit, fingerprints are joined against other sessions in the app, and the face is compared with stored embeddings.

  3. 3

    Explain

    The check lists how many fingerprints and faces were compared, and which sessions matched.

Configuration

The workflow keys and their defaults.

Workflow · json
"duplicate": { "enabled": true, "face_threshold": 0.55, "scope": "app" }
KeyDefaultMeaning
face_threshold0.55Cosine at or above which two faces are the same person for this purpose.
scopeappapp compares within one app (sandbox and live stay apart); org compares across your organisation's apps.

Reference

What is compared, and against what.

What is compared, and against what.
SignalStored asMatches when
Document numberHMAC-SHA256 of org, kind and country:type:numberThe fingerprints are equal
Aadhaar number from card OCRHMAC-SHA256 of org, kind and the numberThe fingerprints are equal
PANHMAC-SHA256 of org, kind and the PANThe fingerprints are equal
Document imageHMAC-SHA256 of org, kind and the front image's SHA-256The same file is uploaded again
FaceSFace 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.

Duplicate detection codes
CodeOutcomeWhen
duplicate_foundreviewA document number, PAN, identical image or face matches another approved, in-review, processing or declined session in scope.
no_identifiersskippedNo 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.