> For the complete documentation index, see [llms.txt](https://docs.4p.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.4p.finance/en/partner/account-onboarding-kyc-kyb.md).

# Account Onboarding (KYC/KYB)

This document describes, step by step, the routes a partner must consume to carry out the complete account opening (KYC) flow at 4Pay from the partner's frontend.

***

## 1. Overview

This API allows a partner to build their own onboarding frontend and guide the end customer through the entire 4Pay account opening flow, including identity validation (KYC) and liveness.

The flow consists of multiple sequential steps. Each step must be completed successfully before moving on to the next one.

### 1.1 Base URLs

**Lab (sandbox):** `https://verify-api.4p.finance/lab/`

{% hint style="warning" %}
**⚠️ Access to the lab environment**

The lab environment requires prior IP whitelisting. Send the origin IP(s) of your requests to the 4Pay team before starting your tests.
{% endhint %}

**Production:** `https://verify-api.4p.finance/`

{% hint style="warning" %}
**🔑 Partner ID — production only**

The Partner ID segment (hash identifying the partner) is used only in the production environment. In the lab, routes are called directly after `/lab/`, without this segment.
{% endhint %}

```
example (lab):        https://verify-api.4p.finance/lab/account/initial_registration
example (production):  https://verify-api.4p.finance/v1/{hash-partner-id}/account/initial_registration
```

In the following sections, each route's path is shown as relative (e.g., `/account/initial_registration`). Build the full URL by combining it with the environment's base URL and, in production, adding the Partner ID right after the domain.

### 1.2 Standard Response Format

All API responses follow the same base format. The most important field for handling the response programmatically is always `info.result` — it uniquely identifies the outcome of the operation, regardless of the value of `success` or `http_code`.

```json
{
  "http_code": 200,
  "success": true,
  "info": {
    "result": "result_identifier",
    "message": "Human-readable message to display or log."
  }
}
```

{% hint style="info" %}
**💡 Integration tip**

Always base your application's logic on `info.result`, not just on `success` or `http_code`. This is because an `http_code` of 200 may contain either success or a business-rule failure (e.g., document already registered). The `message` field is user-friendly and can be shown directly to the end user when appropriate.
{% endhint %}

### 1.3 Integration Architecture

{% hint style="info" %}
**🌐 Direct browser calls — no intermediary backend**

The partner's frontend must call the 4Pay API directly from the end customer's browser, without going through a partner backend/server as an intermediary. This is an architectural requirement: it's what allows 4Pay to apply IP control and risk analysis via WAF directly on the end user's traffic (e.g., IP reputation lists, proxy/VPN/datacenter detection, per-user rate limiting). If requests go through the partner's backend, that visibility is lost.
{% endhint %}

The routes described in this document are partner-specific and have CORS enabled only for the domain registered in the integration setup.

### 1.4 Authentication — reCAPTCHA v3

{% hint style="warning" %}
**🔒 reCAPTCHA scope**

Only the first route in the flow — `/account/initial_registration` — requires the reCAPTCHA v3 token (`rid` field). The other routes in the flow do not require this token.
{% endhint %}

The token must be generated on the partner's frontend using the exclusive site key provided by 4Pay for that partner.

* The domain authorized to generate tokens with this site key is restricted to the partner's registered domain.
* The token is validated on 4Pay's backend (minimum score and expected action).\
  [See the reCAPTCHA v3 documentation](https://developers.google.com/recaptcha/docs/v3?hl=pt-br)

### 1.5 Flow Overview

The steps below must be executed in this order:

1. Initial registration (email and password) — sends an email to confirm ownership of the email address
2. Account opening hash validation — confirms that the hash received in the URL by email is valid
3. Document submission (CPF/CNPJ) — checks whether an account already exists with that document
4. Phone verification code request (WhatsApp or SMS)
5. Verification code validation sent to the phone
6. Address data submission
7. Start of the liveness process (facial validation)
8. Identity document submission (KYC)
9. Final account/KYC status check

***

## 2. Initial Registration

First step of the flow. Creates the customer's initial registration with email and password.

#### **`POST`** `/account/initial_registration`

{% hint style="info" %}
**📧 What happens after this call**

An email is sent to the customer with a confirmation link. The customer needs to click this link to confirm they own the email address and continue the registration. The link contains an `h` (hash) parameter that identifies this account opening — this hash will be reused in the following steps.
{% endhint %}

**Payload (body)**

```json
{
  "email": "customer@example.com",
  "password": "StrongPassword@123",
  "newsletter": 1,
  "rid": "0cAFcW"
}
```

**Response — Success**

```json
{
  "http_code": 200,
  "success": true,
  "info": {
    "result": "successful_registration",
    "message": "Successful registration."
  }
}
```

Payload fields: `email` (customer's email), `password` (password chosen by the customer — see requirements below), `newsletter` (1 = opted in to communications, 0 = declined), `rid` (reCAPTCHA v3 token).

**Password requirements**

The password provided in the `password` field must meet all of the criteria below:

* Minimum of 6 characters.
* Maximum of 20 characters.
* A mix of uppercase and lowercase letters.
* At least one special character (e.g., !, @, #, $).
* At least one number.

***

## 3. Account Opening Hash Validation

Executed when the customer accesses the URL received by email (after clicking the confirmation link). Validates whether the hash for that account opening exists and returns the data already registered so far.

#### **`GET`** `/account/customer_hash/{hash}`

{% hint style="info" %}
**🔑 About the hash**

This is the same hash used in all other steps of the flow — received in the `h` parameter of the confirmation URL sent by email. Here it is sent directly in the URL path, not in the body.
{% endhint %}

**Response — Hash found**

The hash is valid. The `data` field returns the customer's data registered so far, including KYC progress (`status_kyc` and `step_kyc`).

```json
{
  "http_code": 200,
  "success": true,
  "info": {
    "result": "customer_by_hash",
    "message": "Select Customer by Hash.",
    "data": {
      "email": "customer@example.com",
      "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
      "status_kyc": 0,
      "step_kyc": 4
    }
  }
}
```

**Response — Hash not found**

There is no account opening with this hash (it may have expired, already been completed, or be invalid).

```json
{
  "http_code": 200,
  "success": true,
  "info": {
    "result": "customer_hash_empty",
    "message": "Data not found."
  }
}
```

***

## 4. Document Submission (Existence Check)

Step executed as soon as the customer clicks the link received by email and fills in the CPF or CNPJ in the partner's form. This call checks whether a 4Pay account already exists associated with that document.

#### **`POST`** `/account/update_customer`

{% hint style="info" %}
**🔑 About the hash**

The hash is the unique, temporary identifier for that account opening. It comes in the `h` parameter of the URL sent by email (e.g., `https://lab-app.4p.finance/complete-registration?h=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6`) and must be reused in every subsequent step of the flow. This hash is automatically invalidated once the account is completed.
{% endhint %}

**Payload (body)**

```json
{
  "person_document": "111.222.333-44",
  "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "step_kyc": 1
}
```

**Response — Document not found (may proceed)**

No account exists with this document. Registration can continue normally.

```json
{
  "http_code": 200,
  "success": true,
  "info": {
    "details": {
      "result": "document_not_found",
      "message": "Document not found."
    }
  }
}
```

**Response — Document already registered**

A 4Pay account already exists with this document. The flow must be stopped and the customer informed.

```json
{
  "http_code": 200,
  "success": false,
  "info": {
    "details": {
      "result": "document_already_exists",
      "message": "The document sent already exists in the database."
    }
  }
}
```

Payload fields: `person_document` (CPF or CNPJ, always formatted/masked — e.g., `111.222.333-44`), `hash` (account opening identifier), `step_kyc` (fixed at 1 for this step).

{% hint style="warning" %}
Note: on this specific route, the result comes in `info.details.result` (not in `info.result` as with the other routes).
{% endhint %}

***

## 5. Phone Verification Code Request

{% hint style="warning" %}
**⏱️ Code expiration**

The verification code sent via WhatsApp or SMS expires in 5 minutes. After this period, a new code must be requested.
{% endhint %}

{% hint style="success" %}
**✅ WhatsApp is the preferred channel**

Validation via WhatsApp is the recommended method and has approximately a 99% delivery success rate. SMS is offered as an alternative, but tends to have delivery issues — use it only as a fallback if WhatsApp is not available for the customer's number.
{% endhint %}

Sends a verification code to the customer's phone via WhatsApp. Recommended channel.

#### **`POST`** `/account/verification_code`

**Payload (body)**

```json
{
  "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "phone": "5511999998888",
  "type_message": "whatsapp" // "whatsapp" or "sms"
}
```

**Response — Code sent**

```json
{
  "http_code": 200,
  "success": true,
  "info": {
    "result": "code_sent",
    "message": "Verification code successfully sent.",
    "userdata": {
      "phone": "5511999998888"
    }
  }
}
```

***

## 6. Verification Code Validation

Validates the code received by the customer via WhatsApp or SMS.

#### **`POST`** `/account/validation_code`

**Payload (body)**

```json
{
  "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "phone_only_number": "5511999998888",
  "confirmation_code": "123456"
}
```

**Response — Valid code**

```json
{
  "http_code": 200,
  "success": true,
  "info": {
    "result": "valid_code",
    "message": "The code sent is the same as the code generated."
  }
}
```

**Response — Invalid code**

```json
{
  "http_code": 200,
  "success": false,
  "info": {
    "result": "invalid_code",
    "message": "The code sent is the same as the code generated."
  }
}
```

## 7. Address Data Submission

Sends the address data and other complementary registration data for the customer.

#### **`POST`** `/account/update_customer`

{% hint style="info" %}
**📌 Same route as Step 4**

This is the same `/account/update_customer` route used in Step 4, but with a more complete payload and `step_kyc = 4`, indicating progress in the KYC flow.
{% endhint %}

**Payload (body)**

```json
{
  "person_type": "personal", // "personal" for an Individual and "empresarial" for a Legal Entity.
  "person_document": "111.222.333-44", // CPF, or CNPJ if it is a Legal Entity.
  "legal_representative_document": "111.222.333-44", // Sends the legal representative's CPF if person_type is Legal Entity.
  "company_website": "example.com", // Optional.
  "company_activity": "E-commerce", // Optional.
  "post_code": "01310100",
  "address": "Rua das Flores",
  "number_address": "123",
  "neighborhood": "Jardim Exemplo",
  "city": "São Paulo",
  "state": "SP",
  "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "complement_address": "",
  "status_kyc": 0,
  "step_kyc": 4
}
```

**Response — Success**

```json
{
  "http_code": 200,
  "success": true,
  "info": {
    "result": "registration_completed",
    "message": "Registration completed successfully.",
    "data": {
      "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
    }
  }
}
```

The `person_type` field accepts two values: `"personal"` (individual) or `"empresarial"` (legal entity).

{% hint style="danger" %}
**🚨 Important — this is not the end of registration**

Even though the result returns `registration_completed`, registration is not yet finished. The next required step is starting the liveness process.
{% endhint %}

***

## 8. Starting the Liveness Process

Requests the customer's liveness (facial validation) URL, via integration with Didit.

#### **`POST`** `/account/liveness`

**Payload (body)**

```json
{
  "hash": "a1b2g3d4e5f6n1b8c9d0e1f2a3b4c5d6"
}
```

**Response — Success**

```json
{
  "http_code": 200,
  "success": true,
  "info": {
    "result": "liveness_initiated",
    "message": "Liveness process initiated.",
    "data": {
      "required": true,
      "selfie": false,
      "token": "aBcD1234EfGh",
      "sessionId": "11111111-2222-3333-4444-555555555555",
      "url": "https://verify.4payfinance.com/pt-BR/session/abcd123",
      "kyc_provider": false,
      "registration_step": "doc"
    }
  }
}
```

{% hint style="info" %}
**🎥 How to run liveness**

The URL returned in `data.url` must be opened using Didit's Web SDK or Mobile SDK ([Didit SDK Documentation](https://docs.didit.me/integration/web-sdks/overview)). If the customer is on a computer, they can scan a QR code and complete liveness on their phone, then return to the flow on the computer — or, if starting registration directly on the phone, complete everything on that device.

NOTE: On desktop, using Didit's SDK, your frontend monitors the completion of liveness and sends the user to the next step, document submission.
{% endhint %}

***

## 9. Identity Document Submission (KYC)

After liveness is completed, the customer submits the image of their identity document.

#### **`POST`** `/account/complete_kyc`

**File (multipart/form-data)**

Send the keys "files" for the file and "customer" containing the value with a JSON object holding the account's hash.

**Example multipart/form-data:**

```json
files: <document — PDF, JPG, or PNG>
customer: {"hash": "1464703ee9c1cec3958a6154502818e6"}
```

<mark style="color:$danger;">**NOTE:**</mark>

* The document file must be renamed to "document" if it doesn't already have that name.
* It must show the PHOTO and CPF (a driver's license, CNH, is preferred) so that Face Match, OCR, and document forensics can be performed to validate the data and complete KYC.

{% hint style="warning" %}
**📷 Image quality**

The document image must be submitted in good quality — sharp, well-lit, without cropping or glare. Low-quality images increase the chance of KYC rejection (e.g., `kyc_data_is_invalid`).
{% endhint %}

***

## 10. Final Account / KYC Status Check

Checks the account's final status after submission of the KYC document is complete.

#### **`POST`** `/account/kyc_validation`

**Payload (body)**

```json
{
  "hash": "a1b2c3d4e5f1a7b8c0a0e1f2a3b4c5d5"
}
```

**Response — All good (account approved)**

After this call, if the account is complete and validated, the customer receives a confirmation email that their 4Pay account has been opened.

```json
{
  "http_code": 200,
  "success": true,
  "info": {
    "result": "all_data_is_valid"
  }
}
```

**Responses — Account not approved / invalid data**

In these cases, the `info.result` field identifies the specific reason for rejection:

| info.result             | What it means                                                       |
| ----------------------- | ------------------------------------------------------------------- |
| `account_not_approved`  | The account could not be approved at this time.                     |
| `invalid_data`          | Data submitted during registration is invalid, in general.          |
| `invalid_owner_data`    | The account holder's data (individual) is invalid.                  |
| `invalid_company_data`  | The company's data (legal entity) is invalid.                       |
| `kyc_data_is_invalid`   | Data extracted from the KYC process (document/liveness) is invalid. |
| `basic_data_is_invalid` | Basic registration data is invalid.                                 |
| `all_data_is_invalid`   | All submitted data is invalid.                                      |

Example rejection response:

```json
{
  "http_code": 200,
  "success": false,
  "info": {
    "result": "account_not_approved",
    "message": "This account could not be approved at this time."
  }
}
```
