> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.crisscross.money/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.crisscross.money/_mcp/server.

# Verification Sandbox Testing

The sandbox environment lets you exercise account verification without calling real providers. A deterministic sandbox provider resolves every request based on the **last digit of the account identifier** (bank account number or mobile-money number), so you can reproduce each outcome — verified, name mismatch, not found, and the asynchronous pending flow — on demand.

> **Note**
>
> All examples use the current, unified verification endpoint **`POST /v1/accounts/validate`** (poll with `GET /v1/accounts/validate/{id}`). The older `/verification/mobile` and `/verification/bank-account` endpoints are deprecated — see [Mobile Number Verification](/payout-mobile-number-verification) and [Bank Account Verification](/payout-bank-account-verification).

Sandbox verification is available for both `mobile_money` and `bank_account` account types across **Kenya (KEN)**, **Rwanda (RWA)**, **Ghana (GHA)** and **Tanzania (TZA)**. The same last-digit rules apply to every market and both account types.

## How scenarios are triggered

Set the **last digit** of `accountIdentifier` to the scenario you want:

| Last digit    | Resulting `status` | Behaviour                                                                                                                                             |
| ------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `3`           | `not_found`        | No account exists for the identifier. `failureReason` is populated; no name is returned.                                                              |
| `5`           | `name_mismatch`    | The provider returns a *minor permutation* of the `accountHolderName` you supplied.                                                                   |
| `7`           | `pending`          | The provider is still resolving. Poll `GET /v1/accounts/validate/{id}`; the sandbox validation settles on `pending` (it does not flip to `verified`). |
| `9`           | `name_mismatch`    | The provider returns a *different* name (drawn from a fixed lookup, deterministic per identifier).                                                    |
| `0,1,2,4,6,8` | `verified`         | The account is valid. `accountHolderName` you supplied is echoed back; if you supply none, a deterministic name is generated.                         |

**Default behaviour:** any identifier whose last digit is not `3`, `5`, `7` or `9` resolves to `verified`.

## Mobile Number Verification

Send `accountType: mobile_money` and put the recipient's mobile number in `accountIdentifier`. `bankCode` is not required for mobile money.

#### Example: Verified (Kenya)

```json
{
  "merchantId": "123e4567-e89b-12d3-a456-426614174000",
  "country": "KEN",
  "accountType": "mobile_money",
  "accountIdentifier": "254712345670",
  "accountHolderName": "Jane Doe"
}
```

**Response (200 OK):**

```json
{
  "id": "018f4a1b-2c3d-7e4f-8a9b-0c1d2e3f4a5b",
  "status": "verified",
  "accountHolderName": "Jane Doe",
  "provider": "sandbox"
}
```

#### Example: Name mismatch (Ghana)

```json
{
  "merchantId": "123e4567-e89b-12d3-a456-426614174000",
  "country": "GHA",
  "accountType": "mobile_money",
  "accountIdentifier": "233241234569",
  "accountHolderName": "Jane Doe"
}
```

**Response (200 OK):**

```json
{
  "id": "018f4a1b-2c3d-7e4f-8a9b-0c1d2e3f4a5c",
  "status": "name_mismatch",
  "accountHolderName": "Kwame Mensah",
  "provider": "sandbox",
  "failureReason": "Sandbox name mismatch scenario (suffix-9)"
}
```

#### Test numbers by country

Change the final digit to select the scenario (see the table above). The prefixes below are representative sandbox numbers; only the last digit is significant.

| Country            | Example identifier | Verified (`0`) | Not found (`3`) | Pending (`7`)  |
| ------------------ | ------------------ | -------------- | --------------- | -------------- |
| **Kenya (KEN)**    | `2547123456X0`     | `254712345670` | `254712345673`  | `254712345677` |
| **Rwanda (RWA)**   | `2507881234X0`     | `250788123450` | `250788123453`  | `250788123457` |
| **Ghana (GHA)**    | `2332412345X0`     | `233241234560` | `233241234563`  | `233241234567` |
| **Tanzania (TZA)** | `2557123456X0`     | `255712345670` | `255712345673`  | `255712345677` |

## Bank Account Verification

Send `accountType: bank_account`, put the account number in `accountIdentifier`, and include `bankCode`. In sandbox the `bankCode` is not validated against a real institution, so any non-empty value works.

#### Example: Verified (Tanzania)

```json
{
  "merchantId": "123e4567-e89b-12d3-a456-426614174000",
  "country": "TZA",
  "accountType": "bank_account",
  "accountIdentifier": "0100200300400",
  "bankCode": "TZ-CRDB",
  "accountHolderName": "Amina Juma"
}
```

**Response (200 OK):**

```json
{
  "id": "018f4a1b-2c3d-7e4f-8a9b-0c1d2e3f4a5d",
  "status": "verified",
  "accountHolderName": "Amina Juma",
  "provider": "sandbox"
}
```

#### Example: Account not found (Kenya)

```json
{
  "merchantId": "123e4567-e89b-12d3-a456-426614174000",
  "country": "KEN",
  "accountType": "bank_account",
  "accountIdentifier": "0100200300403",
  "bankCode": "KE-EQUITY"
}
```

**Response (200 OK):**

```json
{
  "id": "018f4a1b-2c3d-7e4f-8a9b-0c1d2e3f4a5e",
  "status": "not_found",
  "provider": "sandbox",
  "failureReason": "Sandbox account-not-found scenario (suffix-3)"
}
```

#### Test account numbers by country

Only the last digit is significant; use any `bankCode`.

| Country            | Example `bankCode` | Verified (`0`)  | Name mismatch (`5`) | Not found (`3`) |
| ------------------ | ------------------ | --------------- | ------------------- | --------------- |
| **Kenya (KEN)**    | `KE-EQUITY`        | `0100200300400` | `0100200300405`     | `0100200300403` |
| **Rwanda (RWA)**   | `RW-BK`            | `0100200300400` | `0100200300405`     | `0100200300403` |
| **Ghana (GHA)**    | `GH-STANBIC`       | `0100200300400` | `0100200300405`     | `0100200300403` |
| **Tanzania (TZA)** | `TZ-CRDB`          | `0100200300400` | `0100200300405`     | `0100200300403` |

## Polling pending validations

Any identifier ending in `7` returns `status: pending` with an `id`. Poll until you observe the terminal state:

```bash
curl https://api.crisscross.money/v1/accounts/validate/018f4a1b-2c3d-7e4f-8a9b-0c1d2e3f4a5b \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

Because the sandbox provider is stateless, a `pending` scenario stays `pending` — it is intended to let you exercise the poll loop and your handling of the asynchronous case, not to simulate an eventual success.

## Important notes

* **Sandbox only.** These rules apply only in the sandbox. Production validation uses real account numbers/phone numbers and real providers.
* **Deterministic.** The same identifier always produces the same result (including the generated name for the `5`/`9` mismatch scenarios), so results are stable across retries and consistent with the validation cache.
* **Name matching.** Supply `accountHolderName` to exercise the `name_mismatch` branch. Without it, a `5`/`9` identifier still returns `name_mismatch` using a generated name.
* **Country codes.** Use 3-letter ISO country codes in `country` (`KEN`, `RWA`, `GHA`, `TZA`) — these are country codes, not currency codes.

## Next steps

* [Payout Beneficiaries](/payout-beneficiaries) — how verification feeds beneficiary creation.
* [Verification API Reference](/api-reference/payouts/verification) — full request/response schema for `/accounts/validate`.