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

# Test Travel Rule in Sandbox

> Run every Travel Rule outcome end to end in sandbox against the Didit Test VASP: accept, decline, name mismatch, not reachable, timeout and self-hosted wallets.

A sandbox application exchanges Travel Rule data with one counterparty only: the **Didit Test VASP**, a test VASP built into the platform. It answers each transfer the way you ask it to, through the same code that handles a real counterparty, so you can watch every status, timeline entry and webhook of an exchange before you go live.

Nothing you send in sandbox leaves Didit. No network, email recipient or other Didit customer is ever contacted, and nothing is charged.

## Before you start

1. Use a **sandbox** application's API key. Live applications never reach the test VASP.
2. Turn Travel Rule on with a legal name:

```bash theme={null}
curl -X PUT https://verification.didit.me/v3/travel-rule/settings/ \
  -H "x-api-key: YOUR_SANDBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_enabled": true, "legal_name": "Example CASP Sandbox"}'
```

3. Optionally subscribe a webhook destination to `travel_rule.status.updated`. Sandbox delivers it, marked `"environment": "sandbox"`.

## The Didit Test VASP

`GET /v1/sandbox/scenarios/` returns the test VASP's profile and every scenario in its `travel_rule` object, so your tests can read the trigger values instead of hard-coding them:

| Field | Value |
| - | - |
| `legal_name` | `Didit Test VASP` |
| `lei` | `DIDITESTVASP00000001`, well-formed but not registered |
| `jurisdiction` | `GB` |
| `compliance_email` | An address under `didit.test`, never mailed |
| `travel_address` | A travel address under a `.test` host, never reachable |
| `network_codes` | Test codes that are never dialled |
| `default_scenario` | `accept` |
| `inbound_scenarios` | `["accept", "name_mismatch"]` |

Every value is test data. The test VASP is not a member of any Travel Rule network, and no result in sandbox shows that a real counterparty or network can be reached.

## Outbound scenarios

Every outbound `travelRule` transaction of a sandbox application that needs an exchange goes to the test VASP. Pick the outcome with the beneficiary wallet address (case does not matter), or with a beneficiary name of the form `Didit Test VASP <suffix>`; the wallet wins when both are set. With neither, or with a suffix the table does not list, the transfer completes (`accept`).

A transfer that needs no exchange never reaches the test VASP: one below your `threshold_amount`, one to your user's own verified wallet, and one whose counterparty wallet you declared self-hosted. A counterparty VASP you or your end user picked by hand from the directory is judged by the routes a live application would have: when its entry has one (a verified `TRP` endpoint, a network your application sends on, or a compliance contact), the transfer goes to the test VASP; when it has none, the transfer ends `COUNTERPARTY_VASP_NOT_FOUND`, as it would on a live application.

| Scenario | Name suffix | Wallet (ETH) | Ends at |
| - | - | - | - |
| `accept` | none | `0xc00c4cd93ff83953ce3347b33656d68ab8f35cfb` | `COMPLETED`, then `FINISHED` once you report `payment_txn_id` |
| `decline` | `Decline` | `0x7694c5b0213859ef409107853a36ff7c85835f18` | `COUNTERPARTY_VASP_GENERAL_DECLINE` |
| `name_mismatch` | `Name Mismatch` | `0x501b7a0886e31f516aaf54fa99107d2377bd2d3e` | `COUNTERPARTY_MISMATCHED_DATA`, at your name rule (with `NONE` it completes) |
| `not_reachable` | `Not Reachable` | `0xf0e986c1a1c3ae41629bf5cdcee0a67dd1eb0cce` | `COUNTERPARTY_VASP_NOT_REACHABLE` after the real retry schedule (30 seconds, then 2, 10, 30 and 60 minutes, about an hour and 45 minutes in all), then resendable |
| `timeout` | `Timeout` | `0xb63a1b1d4bee82d109332187cbf5b657eb7da498` | `EXPIRED` on the first overdue check after its one-minute deadline, so within about 15 minutes; your `timeout_outcome` then applies |
| `self_hosted` | `Self Hosted` | `0x33dcc2a7e69cf965f7f4eaf6ba657bcb078a2b65` | `UNCONFIRMED_OWNERSHIP`, then `COMPLETED` once the wallet's ownership proof is recorded |

```bash theme={null}
curl -X POST https://verification.didit.me/v3/transactions/ \
  -H "x-api-key: YOUR_SANDBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_id": "sbx-tr-accept-001",
    "transaction_category": "travelRule",
    "transaction_details": {
      "direction": "outbound",
      "action_type": "transfer",
      "amount": "0.25",
      "currency": "ETH",
      "currency_kind": "crypto"
    },
    "subject": { "entity_type": "individual", "vendor_data": "user-042", "full_name": "Jane Doe" },
    "travel_rule_details": {
      "status": "AWAITING_COUNTERPARTY",
      "beneficiary_data": {
        "wallet_address": "0xc00c4cd93ff83953ce3347b33656d68ab8f35cfb",
        "name": "Carlos Ruiz"
      }
    }
  }'
```

The transfer's `rail` is `TEST_VASP`, and the transfers list names `Didit Test VASP` as its beneficiary VASP. Follow it in the `travel_rule.status.updated` webhooks, or read `GET /v3/transactions/{transaction_id}/`.

* **`name_mismatch`** compares the beneficiary name you sent with the holder name the test VASP answers with, at your name rule for the beneficiary's entity type (`matching_rules.individual.name` or `matching_rules.company.name`, which follow `name_matching_strictness` until you set them). The transfer and the timeline entry that decided it carry `name_match` (with `compared_with: "counterparty_vasp"`) and `data_match` with `mismatched_fields: ["name"]`.
* **`not_reachable`** keeps the transfer `AWAITING_COUNTERPARTY` while it retries, with `delivery.status` `RETRYING` and `last_error` `test VASP is not answering`, then makes it `COUNTERPARTY_VASP_NOT_REACHABLE`. Send `{"action": "resend"}` afterwards to run it again.
* **`self_hosted`** has the test VASP answer that the wallet is self-hosted. Record the proof with the [wallet ownership widget](/transaction-monitoring/wallet-ownership-widget.md) (minted for you while `auto_wallet_verification` is on) or [`POST .../travel-rule/ownership/`](/management-api/travel-rule/confirm-ownership) with `{"confirmed": true}`: the wallet is added to your address book under the beneficiary's name and the name match decides the transfer. A match completes it and marks the wallet verified; `{"confirmed": false}` declines it.

## Inbound transfers from the test VASP

To test the receiving side, have the test VASP send you a transfer with `POST /v3/travel-rule/test-vasp/inbound/`. Name one of your application's own [address-book](/transaction-monitoring/travel-rule.md#wallet-address-book) wallets: the transfer is decided by the same inbound path as a real one, against your address book, your ownership mode and your name rules.

```bash theme={null}
curl -X POST https://verification.didit.me/v3/travel-rule/test-vasp/inbound/ \
  -H "x-api-key: YOUR_SANDBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "beneficiary_wallet_address": "0xYourDepositWallet",
    "scenario": "accept",
    "reference": "sbx-inbound-001"
  }'
```

| Field | Description |
| - | - |
| `beneficiary_wallet_address` | Required, up to 128 characters. Use one of your own address-book wallets: any other wallet is booked `ON_HOLD`. |
| `scenario` | `accept` (default): the test VASP names the wallet's own holder. `name_mismatch`: it names someone no holder agrees with. |
| `chain`, `amount`, `currency` | Optional. Default `ETH`, `1` and `ETH`. |
| `reference` | Optional, up to 128 characters. Sending the same `reference` again returns the first answer (`201`) and books nothing new. |

The `201` response carries `transaction_id`, `txn_id`, `scenario`, `travel_rule_status`, `detail`, `test_vasp: true` and the `travel_rule` transfer. A live application gets `400` (`"The Didit Test VASP is only available on sandbox applications."`), another `scenario` gets `400`, and an application with Travel Rule off gets `409`.

| Your wallet entry | `accept` | `name_mismatch` |
| - | - | - |
| None on that chain | `ON_HOLD` | `ON_HOLD` |
| Ownership not proven for your [ownership mode](/transaction-monitoring/travel-rule.md#ownership-validation-modes) | `UNCONFIRMED_OWNERSHIP` | `UNCONFIRMED_OWNERSHIP` |
| Verified | `COMPLETED` while `inbound_auto_accept` is on, otherwise `ON_HOLD` | `COUNTERPARTY_MISMATCHED_DATA` (at name rule `NONE`, as `accept`) |

Decide a transfer that waits with [`POST .../travel-rule/ownership/`](/management-api/travel-rule/confirm-ownership), as you would a real one: `{"confirmed": true}` completes it and `{"confirmed": false}` declines it.

A deposit you report yourself with `POST /v3/transactions/` is not one the test VASP sent: it stays `AWAITING_COUNTERPARTY` waiting for its originating VASP, and its timeline names the endpoint above. It fires no webhook until it expires after `confirmation_timeout_hours`. [`POST /v3/travel-rule/inbound/`](/management-api/travel-rule/register-inbound) still settles an after-deposit registration against your address book, as on a live application.

## Webhooks in sandbox

`travel_rule.status.updated` fires for sandbox applications on every status change, with `"environment": "sandbox"`. A transfer the test VASP answered also carries `rail: "TEST_VASP"`, `test_vasp: true` and `test_vasp_scenario`. The rest of sandbox still behaves as described in [Transaction Monitoring in sandbox](/integration/sandbox-testing.md#transaction-monitoring-in-sandbox): rules are not evaluated, `transaction.created` and `transaction.status.updated` are not sent, and nothing is billed.

## What sandbox does not prove

* That a real counterparty, a network membership or Didit's public TRP host works. None of them is contacted.
* That your network credentials are valid. A connected membership is proven by its first live exchange.
* The [directory](/transaction-monitoring/travel-rule.md#directory) routes of real VASPs: on a sandbox application every route reads `unavailable` with `reason: sandbox_application`, and every row `reachable: false`.

See [Travel Rule networks](/transaction-monitoring/travel-rule-networks.md) for what each rail reaches in production today.


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