# Duplicate detection

> Stop the same person or business from being verified twice in a project — one person, one account.

Source: https://docs.okyc.stream/concepts/duplicates

Add a **Duplicate check** step to a workflow (in the builder: **Automatic checks → Add Duplicate check**). After
the applicant submits, it looks for earlier sessions in the same [project](https://docs.okyc.stream/concepts/projects.md) that look like the
same person or business, then does what the step says: send the session to review (the default), decline it, or
only record the match.

## What it matches on

| Signal                   | Matches when                                                                                                           |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `tax_id`                 | The same verified tax ID (PAN in India)                                                                                |
| `bank_account`           | The same verified bank account                                                                                         |
| `vpa`                    | The same verified UPI ID                                                                                               |
| `business_id`            | The same GSTIN, CIN, LLPIN or Udyam number (KYB)                                                                       |
| `phone`                  | The same verified mobile number                                                                                        |
| `email`                  | The same verified email address                                                                                        |
| `name_dob`               | The same verified name and date of birth, in the same country — case, punctuation, spacing and word order don't matter |
| `face` / `face_possible` | The selfie matches (or closely resembles) an earlier applicant's face — only with face search on, see below            |

Every signal is an exact match on a keyed hash that OKYC keeps per project — values are never compared in the
clear. Aadhaar numbers are never used: OKYC never stores them. Nothing is matched across projects or across
customers.

## Face search

Turn on **Match faces** in the step to also compare the selfie with earlier applicants' faces in the project. It
catches the same person applying with different documents.

* **Consent.** The consent step then asks the applicant to accept one more purpose — checking their selfie against
  earlier verifications with you. Face search only runs for applicants who accepted it.
* **Two thresholds.** From &#x2A;*90%** similarity (by default) the faces count as the same person, `face` is added to
  the match and the step's action applies. From &#x2A;*70%** up to that, the match is listed as `face_possible` and the
  session goes to review, whatever the action (unless it is *Record only*). Both are adjustable in the step.
* **Stored encrypted.** OKYC keeps a face template (a list of numbers describing the face, not the photo) for later
  comparisons, encrypted with the project's key and compared inside OKYC — never returned by the API. It is
  deleted with the session's evidence at the end of retention, and when you erase the session.

Reviewers see the similarity next to each face match in the dashboard; the API shows only the signal.

## Never a duplicate

* **The same `reference_id`.** Your own user verifying again (a re-KYC, a retry) is not a duplicate. Pass your
  user's id as `reference_id` when you create a session.
* **Test and live.** They never match each other.
* **Sessions that didn't finish.** By default only **approved** sessions count; set *Compare against* to **Any
  decided session** to include declined sessions and those in review. Cancelled and expired sessions never count.
* **Erased data.** When you erase a session's personal data, its identifiers and hashes go too.

## Step settings

| Setting         | Values                                                             | Default           |
| --------------- | ------------------------------------------------------------------ | ----------------- |
| On a match      | Send to review · Decline (reason `duplicate_person`) · Record only | Send to review    |
| Compare against | Approved sessions · Any decided session                            | Approved sessions |
| Match on        | Any of the signals above                                           | All of them       |
| Look back       | All time · 30 days · 90 days · 1 year                              | All time          |
| Match faces     | On · Off; same person from 85–95%, review from 60–80% similarity   | Off; 90% and 70%  |

Decision rules can use the result as well: `checks.identity.duplicate.matched` (true or false) and
`checks.identity.duplicate.count` (how many earlier sessions matched).

## In the API and the dashboard

Every session carries `duplicates`: the matching sessions' ids and the signals that matched, and nothing else —
no personal data. It is empty for workflows without a duplicate check. In the dashboard, the session shows
*Possible duplicate* with links to the earlier sessions, and **Sessions → Duplicates only** lists every session
that matched.

## In test mode

Every developer uses the same sandbox values, so in test mode only the sandbox's **duplicate PAN** matches (see
[sandbox values](https://docs.okyc.stream/get-started/sandbox.md)): verify one test session with it, then a second one with a different
`reference_id` — the second is a duplicate. Ordinary sandbox values never match.

Face search works for real in test mode with your own camera selfies — verify yourself twice under different
`reference_id`s to see it. The sample selfie from **Use test data** never enters face search.
