> ## Documentation Index
> Fetch the complete documentation index at: https://docs.proofage.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Face re-verification

> Confirm that a person is the one who passed an earlier ID verification: a selfie-only check against their KYC face, decided on the spot, never billed.

A face re-verification answers one question: **is the person in front of the camera the same person who passed an earlier ID verification?** The person only takes a selfie, with the usual liveness check. ProofAge compares it with the face from the earlier verification and decides at once: `approved` or `declined`. No document is asked for, and there is no waiting for a reviewer.

Re-verifications are not billed.

## When to use it

Use it when a person verified once, and later you want to be sure the same person is back:

* **Before an exam or an assessment.** A student passes a full ID verification when they enrol. Before each exam, you send a re-verification, so that someone else is less likely to sit it.
* **Before a high-value action**, such as a payout, a change of bank details or a password reset.
* **When something looks off**, such as a new device or an unusual location. A re-verification is quick and cheap enough to send again.

It makes it harder for someone else to take a user's place. It does not stop every kind of cheating, and it does not watch the person during the exam. You choose when to send one.

It is not a way to find out who a face is. ProofAge only compares the selfie with the one verification you name (1:1). It never searches other verifications.

## What you need

The reference is the earlier verification you compare with. It must be:

* in **the same workspace**, test or live, as the re-verification;
* `approved`;
* a verification that read an ID **document**: an Identity (KYC) verification, or an age verification done with an ID document. A facial age estimation cannot be a reference, and neither can another re-verification. Always point to the original verification.
* still holding its face. ProofAge keeps the face of a verification for 12 months after its decision, or for the workspace's [retention period](https://docs.proofage.net/console/data-retention.md) if that is longer, and deletes it sooner when the verification is [erased](https://docs.proofage.net/console/data-retention.md#erasing-a-persons-data).

You also need to create the session **from your server**, with a [signed request](https://docs.proofage.net/getting-started/api-authentication.md). An unsigned request is refused, so that nobody holding only your public key can start sessions.

## How it works

**1. Create the session**

Your server calls `POST /v1/verifications` with `reference_verification_id`, the `id` of the earlier verification. You get a verification back with a `url`, as for any other session.

**2. The person opens the link**

Send the person to `url`. They accept a short consent text written for this check, then take a selfie with the liveness check your workspace uses. There is no document step.

**3. ProofAge decides**

The new selfie is compared with the face of the reference verification. If the person is live and the faces match, the verification is `approved`. If the faces do not match, it is `declined` with the reason `reference.face.mismatch`. A doubtful match is declined too: there is no manual review.

**4. You get the result**

Your webhook URL receives the decision, as for any verification. The webhook and the API both carry a `reauth` object that names the reference. The decision is the answer: no score is returned.

The person takes the check in ProofAge's hosted page only. The calls for [capturing in your own UI](https://docs.proofage.net/integration/web/custom-ui.md) refuse a re-verification with `409 REAUTH_HOSTED_ONLY`.

## Create a re-verification

Sign the request as for any call. This is the body:

```json theme={null}
{
  "external_id": "student-42",
  "reference_verification_id": "550e8400-e29b-41d4-a716-446655440000"
}
```

```http HTTP theme={null}
POST /v1/verifications
X-API-Key: pk_live_...
X-HMAC-Signature: 3f1c...e9
Content-Type: application/json

{"external_id":"student-42","reference_verification_id":"550e8400-e29b-41d4-a716-446655440000"}
```

```ts Node.js theme={null}
const verification = await client.verifications().create({
  external_id: 'student-42',
  reference_verification_id: '550e8400-e29b-41d4-a716-446655440000',
});

// send the person to verification.url
```

```python Python theme={null}
verification = client.verifications.create(
    external_id="student-42",
    reference_verification_id="550e8400-e29b-41d4-a716-446655440000",
)

# send the person to verification.url
```

```php PHP theme={null}
$verification = $client->verifications()->create([
    'external_id' => 'student-42',
    'reference_verification_id' => '550e8400-e29b-41d4-a716-446655440000',
]);

// send the person to $verification['url']
```

The answer is the usual `201` verification, plus `reauth`:

```json theme={null}
{
  "id": "8a3f4c1e-5b7d-4e2a-9c61-0d2f7e9b1a34",
  "external_id": "student-42",
  "external_metadata": null,
  "redirect_url": "https://example.com/callback",
  "status": "created",
  "reason": null,
  "duplicate_check": { "checked": false, "duplicate_count": 0, "duplicates": [] },
  "erasure": null,
  "consent_accepted_at": null,
  "created_at": "2026-11-13T09:00:00+00:00",
  "updated_at": "2026-11-13T09:00:00+00:00",
  "reauth": {
    "reference_verification_id": "550e8400-e29b-41d4-a716-446655440000",
    "reference_verified_at": "2026-05-02T10:14:00+00:00"
  },
  "url": "https://idv.proofage.net/q/AB12cd34Ef"
}
```

`external_id`, `external_metadata`, `metadata` and `callback_url` work as for any verification. `external_id` is your own: ProofAge does not copy it from the reference and does not compare the two. If your users have one `external_id` each, send the same one, and check on your side that the reference belongs to that user.

## The `reauth` object

Only a re-verification has a `reauth` object. Every other verification has no `reauth` key at all, in the API and in webhooks, so test for the key, not for a type.

| Field | What it holds |
| - | - |
| `reference_verification_id` | The earlier verification the face was compared with. `null` only if that verification has since been deleted. |
| `reference_verified_at` | When the reference verification was approved. ProofAge sets no age limit of its own beyond the face retention period (see [Limits](#limits)), so you decide how old a reference may be. |

ProofAge compares the faces and applies its own threshold. The threshold is ProofAge's, is not exposed and cannot be changed per workspace, and ProofAge may tune it as it learns from real data. No similarity score is returned: decide from `status` and `reason`.

Nothing from the reference's document comes back: no names, no date of birth, no document number. A re-verification has no age result, and its webhook has no `fingerprint_signals`. The `document` object of its webhook is there, with every field `null`.

## The result

The webhook is the [decision webhook](https://docs.proofage.net/api-reference/webhook-decision.md), with a `reauth` object. An approved re-check:

```json approved theme={null}
{
  "verification_id": "8a3f4c1e-5b7d-4e2a-9c61-0d2f7e9b1a34",
  "event": "status.updated",
  "status": "approved",
  "external_id": "student-42",
  "external_metadata": null,
  "reason": null,
  "timestamp": "2026-11-13T09:02:00+00:00",
  "document": {
    "type": null,
    "issuing_country": null,
    "issuing_subdivision": null,
    "fields": {
      "first_name": null,
      "middle_name": null,
      "last_name": null,
      "date_of_birth": null,
      "gender": null,
      "nationality": null,
      "place_of_birth": null,
      "address": null,
      "document_number": null,
      "issue_date": null,
      "expiry_date": null
    }
  },
  "reauth": {
    "reference_verification_id": "550e8400-e29b-41d4-a716-446655440000",
    "reference_verified_at": "2026-05-02T10:14:00+00:00"
  }
}
```

A re-check that did not match differs in `status` and `reason`:

```json declined (faces do not match) theme={null}
{
  "verification_id": "8a3f4c1e-5b7d-4e2a-9c61-0d2f7e9b1a34",
  "event": "status.updated",
  "status": "declined",
  "reason": "reference.face.mismatch",
  "reauth": {
    "reference_verification_id": "550e8400-e29b-41d4-a716-446655440000",
    "reference_verified_at": "2026-05-02T10:14:00+00:00"
  }
}
```

The `document`, `external_id`, `external_metadata` and `timestamp` fields are as in the first example. You can also read the same result with `GET /v1/verifications/{id}`.

These are the statuses and reasons to handle:

| Status | Reason | What it means |
| - | - | - |
| `approved` | `null` | The person is live and their face matches the reference. |
| `declined` | `reference.face.mismatch` | The faces do not match closely enough. |
| `declined` | `reference.unavailable` | After you created the session, the reference was erased or reversed, or its face reached the end of its retention period, so there is nothing to compare with. |
| `declined` | a liveness or selfie reason, such as `selfie.liveness.failed` | The selfie failed the live-person check. See [Decision reasons](https://docs.proofage.net/core-technology/decision-reasons.md). |
| `resubmission_requested` | a selfie reason, such as `selfie.face.not_clear` | The selfie was not usable. The person retakes it in the same verification, as in [Handle a resubmission](https://docs.proofage.net/recipes/handle-resubmission.md). |

A live re-verification never goes to `review`. Attempts are limited, as for any verification: when they run out, it is `declined` with the last attempt's reason.

A decline is a reason to look closer, not proof of cheating. Poor light, a different haircut or new glasses can make a genuine match fail. The usual next step is to send another re-verification. You can create up to 10 against the same reference in 24 hours (see [Limits](#limits)).

## Errors

These come from `POST /v1/verifications` when you send `reference_verification_id`. Every body is `{"error": {"code": "...", "message": "..."}}`, except the 402, which is the flat [billing error](https://docs.proofage.net/integration/error-handling.md#creating-a-verification).

| HTTP | Code | What to do |
| - | - | - |
| 401 | `MISSING_SIGNATURE` | Sign the request. A re-verification cannot be created without a signature. |
| 404 | `REFERENCE_VERIFICATION_NOT_FOUND` | No verification with this ID exists in this workspace. The answer is the same when it exists in another workspace, so check that you use the keys of the workspace that holds it. |
| 422 | `REFERENCE_NOT_APPROVED` | The reference is not `approved`. Only an approved verification can be re-checked. |
| 422 | `REFERENCE_NOT_SUPPORTED` | The reference is not a document verification. A facial age estimation is not one, even if it asked the person for an ID, and neither is another re-verification. Use the person's KYC or document-based age verification. |
| 422 | `REFERENCE_UNAVAILABLE` | The reference has been erased, or its face was deleted at the end of its retention period. The person needs a new ID verification. |
| 429 | `REAUTH_RATE_LIMITED` | 10 re-verifications of this reference were already created in the last 24 hours, so the 11th is refused. See [Limits](#limits). |
| 402 | `PAYMENT_METHOD_REQUIRED` | As for any verification: a live workspace needs a payment method on the account, even though re-verifications themselves are free. |

A person who opens a re-verification with the calls for your own capture UI gets `409 REAUTH_HOSTED_ONLY` from consent, upload and submit.

## Test workspaces

A test workspace analyses nothing, so there is no face to compare. It works like any test verification:

* Create it as above. The reference must still exist in the same test workspace, be approved and be a document verification. In a test workspace, approve a test KYC first with a [test outcome](https://docs.proofage.net/integration/sandbox-testing.md#set-the-outcome-from-your-code).
* The reference does not need to hold a face.
* When the person submits, the re-verification waits in `review`, and you set its outcome with the console, the MCP tool `set-test-verification-outcome` or `POST /v1/verifications/{id}/test-outcome`.
* `set-test-verification-outcome` (or the console, or the test-outcome call) decides it: `approved`, or `declined`. No faces are compared and no score is produced.

## Limits

* **10 re-verifications per reference in 24 hours.** The 11th answers `429 REAUTH_RATE_LIMITED`, until the oldest one is 24 hours old. Unlike [`RATE_LIMIT`](https://docs.proofage.net/integration/rate-limiting), it carries no `Retry-After` and does not clear within seconds, so do not retry it in a loop. This stops re-verifications from being used as a free selfie check.
* **Hosted page only.** The person completes the check at the `url` you send them.
* **One reference per re-verification**, in the same workspace. A reference from another workspace is not found, even in the same organization.
* **Face retention.** A reference works for 12 months after its decision, or for the workspace's retention period if that is longer. After that, create a new ID verification.
* **No billing.** A re-verification is never billed and never uses your free verifications, in live workspaces too.

## What does not run

A re-verification only checks live presence and the match with the reference. It does not run [duplicate detection](https://docs.proofage.net/core-technology/duplicate-detection.md), the [face blocklist](https://docs.proofage.net/core-technology/face-blocklist.md), device checks or any document check, and it does not collect a device fingerprint. A person on a shared exam computer is never declined or blocklisted because of the device.

Webhook subscriptions created with `POST /v1/webhook-subscriptions` (Zapier, for example) do not receive re-verifications, so an automation like "on declined, suspend the user" does not fire on a failed re-check. Your workspace's webhook URL does receive them.

## In the console and with MCP

* The [console](https://docs.proofage.net/console/verifications-and-review.md) cannot create a re-verification. In the verifications list a re-verification has a **Re-check** badge and a link to its reference. The reference's page lists its **Re-checks**, with date and result.
* The MCP tool `create-verification` takes `reference_verification_id` and follows the same rules and error codes, which is handy for an AI agent that integrates for you. `get-verification` and `list-verifications` return the `reauth` object.
* Re-verifications are left out of the dashboard's conversion and approval figures, which count the verifications that people take as a first check.

## Consent and privacy

The person sees a consent text for this check, which says that their face is compared with the photo from their earlier identity verification. It is separate from the KYC and age texts, which talk about an ID document or an age threshold. The comparison uses the face data kept from the earlier verification, so the person must not have asked you to erase it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.