> For the complete documentation index, see [llms.txt](https://developer.fraudcheck.co.za/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.fraudcheck.co.za/identity/digital-onboarding-verification.md).

# Digital Onboarding Verification

Digital Onboarding Verification combines selfie-based liveness verification with identity matching against records already on file.

## When to use this

Use this service to:

* Confirm that a consumer completing an onboarding journey is a live person
* Verify that the consumer’s submitted name and identity number match records on file
* Support remote account opening or customer registration without requiring an in-person identity check
* Reduce identity fraud risk during digital onboarding through liveness and identity verification

## How it works

Submit the consumer’s identity number, first name and surname to generate a secure liveness verification link. You can either share the generated link with the consumer or use the SMS endpoint to send the link directly to their mobile number.

The consumer opens the link and completes a selfie-based liveness check on their device. Once complete, Fraudcheck retrieves the identity verification result, compares the returned identity details against the submitted information, and returns a final outcome of `ACCEPT`, `REJECT` or `FAIL`.

You can retrieve the completed result by polling with the `transaction_id`, or receive it automatically through a callback URL when `trigger_callback` is set to `true`.

| Property              | Value                                                                              |
| --------------------- | ---------------------------------------------------------------------------------- |
| Response type         | `Synchronous`                                                                      |
| Typical response time | 2–4s (onfile faster than online)                                                   |
| Result retrieval      | Returned inline; also retrievable via `GET /enhanced-idv/results/{transaction_id}` |
| Consent required      | `Yes`                                                                              |
| Region                | South Africa                                                                       |

## Authentication

```http
Authorization: Bearer {your_access_token}
```

## Endpoints

```http
POST /consumer-service/dovs/link
POST /consumer-service/dovs/sms
GET  /consumer-service/dovs/status/{transaction_id}
```

## Request

### Headers

| Header          | Required | Value              |
| --------------- | -------- | ------------------ |
| `Authorization` | Yes      | `Bearer {token}`   |
| `Content-Type`  | Yes      | `application/json` |

### Body parameters

<table><thead><tr><th width="207.6666259765625">Field</th><th width="135">Type</th><th align="right">Required</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td><code>IdentityNo</code></td><td>string</td><td align="right">Yes</td><td>—</td><td>Consumer’s South African identity number</td></tr><tr><td><code>FirstName</code></td><td>string</td><td align="right">Yes</td><td>—</td><td>Consumer’s first name</td></tr><tr><td><code>LastName</code></td><td>string</td><td align="right">Yes</td><td>—</td><td>Consumer’s surname</td></tr><tr><td><code>trigger_callback</code></td><td>boolean</td><td align="right">No</td><td><code>false</code></td><td>When <code>true</code>, the completed result is posted to <code>callback_url</code></td></tr><tr><td><code>callback_url</code></td><td>string</td><td align="right">No</td><td>—</td><td>Your HTTPS endpoint for receiving the completed result</td></tr><tr><td><code>name_match_threshold</code></td><td>float</td><td align="right">No</td><td><code>0.85</code></td><td>First-name similarity threshold from <code>0.0</code> to <code>1.0</code></td></tr><tr><td><code>ConsentObtainedByDataSubject</code></td><td>boolean</td><td align="right">yes</td><td>—</td><td>Confirms that consent has been obtained from the consumer</td></tr></tbody></table>

### Example request

```json
{
  "IdentityNo": "*************",
  "FirstName": "John",
  "LastName": "Smith",
  "trigger_callback": true,
  "callback_url": "https://yourapp.com/webhook/dovs",
  "name_match_threshold": 0.85,
  "ConsentObtainedByDataSubject": true
}
```

## Response

### Response fields

| Field              | Type   | Description                                                        |
| ------------------ | ------ | ------------------------------------------------------------------ |
| `unique_url`       | string | Secure URL that the consumer uses to complete the liveness check   |
| `unique_reference` | string | Verification session reference                                     |
| `transaction_id`   | string | Fraudcheck transaction ID used to retrieve or correlate the result |

### Example response

```json
{
  "unique_url": "https://verification-provider.example/session/unique-link",
  "unique_reference": "e240bdc4a51b4f8c8cd3b63e04e7fc94",
  "transaction_id": "a7a7d054127f8e36e10638001385526e"
}
```

## Send liveness link by SMS

Use this endpoint to send the liveness verification link directly to the consumer’s mobile number.

```http
POST /consumer-service/dovs/sms
```

This endpoint accepts the same fields as `/dovs/link`, with `CellNo` required. A `unique_url` is not returned because the link is sent directly to the consumer

### Additional required field

| Field    | Type   | Required | Description                                        |
| -------- | ------ | -------: | -------------------------------------------------- |
| `CellNo` | string |      Yes | Consumer’s mobile number, for example `082123**67` |

### Example request

```json
{
  "IdentityNo": "*************",
  "FirstName": "John",
  "LastName": "Smith",
  "CellNo": "0821234567",
  "AccountID": 123,
  "trigger_callback": false,
  "name_match_threshold": 0.85,
  "ConsentObtainedByDataSubject": true
}
```

### Example request

```http
{
  "IdentityNo": "*************",
  "FirstName": "John",
  "LastName": "Smith",
  "CellNo": "0821234567",
  "AccountID": 123,
  "trigger_callback": false,
  "name_match_threshold": 0.85,
  "ConsentObtainedByDataSubject": true
}
```

### Example response

<pre class="language-http"><code class="lang-http"><strong>{
</strong>  "unique_reference": "e240bdc4a51b4f8c8cd3b63e04e7fc94",
  "transaction_id": "a7a7d054127f8e36e106**001385526e"
}
</code></pre>

### Poll for verification status

Use the `transaction_id` returned when the verification was initiated to check whether the consumer has completed the journey and whether a final result is available.

```http
GET /consumer-service/dovs/status/{transaction_id}
```

### Path parameter

| Parameter        | Type   | Required | Description                                            |
| ---------------- | ------ | -------: | ------------------------------------------------------ |
| `transaction_id` | string |      Yes | Transaction ID returned by `/dovs/link` or `/dovs/sms` |
|                  |        |          |                                                        |

### Example response (Pending)

```http
{
  "success": false,
  "transaction_id": "a7a7d054127f8e36e1***8001385526e",
  "code": null,
  "message": "Verification is still in progress.",
  "is_complete": false,
  "request": null,
  "record": null,
  "outcome": null
}
```

### Example response (Accepted)

```http
{
  "success": true,
  "transaction_id": "a7a7d054127f8e3**10638001385526e",
  "code": "ACCEPT",
  "message": "Identity verification passed",
  "is_complete": true,
  "request": {
    "id_number": "9001015009087",
    "first_name": "John",
    "last_name": "Smith"
  },
  "record": {
    "name": "JOHN",
    "surname": "SMITH",
    "deceased_status": "Alive",
    "deceased_date": null,
    "id_book_issued_date": "2005-03-01",
    "id_card_issued": "Y",
    "id_card_date": "2015-06-12",
    "liveness_code": "1",
    "home_affairs_image": "base64_encoded_official_portrait...",
    "image": "base64_encoded_consumer_selfie..."
  },
  "outcome": {
    "liveness_passed": true,
    "surname_matched": true,
    "first_name_matched": true,
    "verification_passed": true,
    "first_name_threshold": 0.85,
    "response_code": 9,
    "resp_message": "Matched"
  }
}
```

### Example response (Rejected)

```http
{
  "success": false,
  "transaction_id": "a7a7d054127f8e***0638001385526e",
  "code": "REJECT",
  "message": "Identity verification failed",
  "is_complete": true,
  "request": {
    "id_number": "9001015009087",
    "first_name": "John",
    "last_name": "Smith"
  },
  "record": {
    "name": "PETER",
    "surname": "JONES",
    "deceased_status": "Alive",
    "deceased_date": null,
    "id_book_issued_date": null,
    "id_card_issued": null,
    "id_card_date": null,
    "liveness_code": "1",
    "home_affairs_image": "base64_encoded_official_portrait...",
    "image": "base64_encoded_consumer_selfie..."
  },
  "outcome": {
    "liveness_passed": true,
    "surname_matched": false,
    "first_name_matched": false,
    "verification_passed": false,
    "first_name_threshold": 0.85,
    "response_code": 9,
    "resp_message": "Matched"
  }
}
```

### Response fields

| Field            | Type           | Description                                                               |
| ---------------- | -------------- | ------------------------------------------------------------------------- |
| `success`        | boolean        | `true` only when the liveness and identity verification requirements pass |
| `transaction_id` | string         | Fraudcheck transaction ID                                                 |
| `code`           | string \| null | Final outcome: `ACCEPT`, `REJECT`, `FAIL`, or `null` while pending        |
| `message`        | string         | Human-readable verification status                                        |
| `is_complete`    | boolean        | `false` while awaiting completion; `true` when a result is available      |
| `request`        | object \| null | Identity details submitted at initiation                                  |
| `record`         | object \| null | Identity and image data returned once verification is complete            |
| `outcome`        | object \| null | Liveness, matching and verification decision fields                       |

### Verification outcomes

| **Code** | `success` | Meaning                                                                                          |
| -------- | --------: | ------------------------------------------------------------------------------------------------ |
| `ACCEPT` |    `true` | Liveness passed and the submitted name details matched the returned identity record              |
| `REJECT` |   `false` | Liveness failed or the submitted name details did not meet the required match rules              |
| `FAIL`   |   `false` | The verification could not be completed because of a technical or upstream service failure       |
| `null`   |   `false` | The consumer has not completed the verification journey or the final result is not yet available |

## Callback delivery

When `trigger_callback` is set to `true` and a valid `callback_url` is provided, Fraudcheck posts the completed verification result to your endpoint once it becomes available.

The callback payload follows the same structure as a completed `/dovs/status/{transaction_id}` response.

### Callback requirements

Your callback endpoint must:

* Accept `POST` requests with `Content-Type: application/json`
* Be reachable through HTTPS
* Return any `2xx` HTTP response when the payload has been received successfully
* Be able to handle retry attempts where delivery is unsuccessful

| Approach | When to use                                                                                     |
| -------- | ----------------------------------------------------------------------------------------------- |
| Callback | Use when you have a publicly reachable HTTPS endpoint and want to receive results automatically |
| Polling  | Use when you do not have an inbound webhook or prefer to retrieve results on demand             |

## Errors

| HTTP | Code            | Meaning                              | Action                               |
| ---- | --------------- | ------------------------------------ | ------------------------------------ |
| 401  | `unauthorized`  | Token missing or expired             | Refresh your access token            |
| 403  | `forbidden`     | Account lacks access to this service | Contact your account manager         |
| 422  | `invalid_input` | Required field missing or invalid    | See `details.field`                  |
| 500  | `server_error`  | Unexpected error                     | Retry; contact support if persistent |
|      |                 |                                      |                                      |

A pending result is not an error. While the consumer has not completed the verification journey, the status endpoint returns `is_complete: false` and `code: null`.

A completed `REJECT` outcome is also not an API error. It means the verification request was processed successfully, but the consumer did not meet the verification requirements.

## Code examples

{% tabs %}
{% tab title="cURL" %}

```bash
BASE_URL="https://api.fraudcheckonline.co.za/consumer/consumer-service"

curl -X POST "$BASE_URL/dovs/link" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "IdentityNo": "9001015009087",
    "FirstName": "John",
    "LastName": "Smith",
    "ConsentObtainedByDataSubject": true,
    "trigger_callback": false,
    "name_match_threshold": 0.85
  }'
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

BASE_URL = "https://api.fraudcheckonline.co.za/consumer/consumer-service"

initiation_response = requests.post(
    f"{BASE_URL}/dovs/link",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={
        "IdentityNo": "9001015009087",
        "FirstName": "John",
        "LastName": "Smith",
        "ConsentObtainedByDataSubject": True,
        "trigger_callback": False,
        "name_match_threshold": 0.85,
    },
)

initiation_response.raise_for_status()
initiation_data = initiation_response.json()

print(f"Share this URL with the consumer: {initiation_data['unique_url']}")

transaction_id = initiation_data["transaction_id"]

status_response = requests.get(
    f"{BASE_URL}/dovs/status/{transaction_id}",
    headers={"Authorization": f"Bearer {access_token}"},
)

status_response.raise_for_status()
result = status_response.json()

if not result["is_complete"]:
    print("Verification is still in progress.")
elif result["code"] == "ACCEPT":
    print("Verification passed.")
elif result["code"] == "REJECT":
    print("Verification did not pass.")
else:
    print("Verification could not be completed.")
```

{% endtab %}

{% tab title="JavaScript (Node.js)" %}

```javascript
const BASE_URL = "https://api.fraudcheckonline.co.za/consumer/consumer-service";

const initiationResponse = await fetch(`${BASE_URL}/dovs/link`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    IdentityNo: "9001015009087",
    FirstName: "John",
    LastName: "Smith",
    ConsentObtainedByDataSubject: true,
    trigger_callback: false,
    name_match_threshold: 0.85,
  }),
});

if (!initiationResponse.ok) {
  throw new Error(`Request failed: ${initiationResponse.status}`);
}

const initiationData = await initiationResponse.json();

console.log(`Share this URL with the consumer: ${initiationData.unique_url}`);

const statusResponse = await fetch(
  `${BASE_URL}/dovs/status/${initiationData.transaction_id}`,
  {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  }
);

if (!statusResponse.ok) {
  throw new Error(`Status request failed: ${statusResponse.status}`);
}

const result = await statusResponse.json();

if (!result.is_complete) {
  console.log("Verification is still in progress.");
} else if (result.code === "ACCEPT") {
  console.log("Verification passed.");
} else if (result.code === "REJECT") {
  console.log("Verification did not pass.");
} else {
  console.log("Verification could not be completed.");
}
```

{% endtab %}
{% endtabs %}

## Retrieve the verification result

```http
BASE_URL="https://api.fraudcheckonline.co.za/consumer/consumer-service"
TRANSACTION_ID="a7a7d054127f8e36e10638001385526e"

curl -X GET "$BASE_URL/dovs/status/$TRANSACTION_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

## Compliance & consent

* Obtain explicit consent from the data subject before submitting a request.
* Set `ConsentObtainedByDataSubject` to `true` to confirm consent has been captured.
* Retain proof of consent and any retrieved photographs in line with POPIA requirements.

## FAQ

<details>

<summary>Does this service return a result immediately?</summary>

No. The consumer must first open the secure verification link and complete the liveness journey. Once completed, retrieve the result through polling or receive it through your callback endpoint

</details>

<details>

<summary>Should I use the link endpoint or the SMS endpoint?</summary>

Use `/dovs/link` when you want to distribute the secure link through your own channel. Use `/dovs/sms` when you want the verification link sent directly to the consumer’s mobile number.

</details>

<details>

<summary>What does an <code>ACCEPT</code> result mean?</summary>

An `ACCEPT` result means the consumer passed the liveness check and the submitted name details matched the identity record according to the configured rules.

</details>

<details>

<summary>What does a <code>REJECT</code> result mean?</summary>

A `REJECT` result means that the verification was completed, but the liveness or identity matching requirements were not met

</details>

<details>

<summary>What images are returned after completion?</summary>

Where available, the completed result includes the official identity image returned from the identity record and the selfie image captured during the consumer’s liveness journey.

</details>

## Changelog

| Date       | Version | Change           |
| ---------- | ------- | ---------------- |
| 2026-05-26 | v1.0    | Initial release. |
