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

# Sandbox testing

> Test every flow without real money, using mock endpoints and testnet funds.

The sandbox runs the same API as production, with test data and no real money. Mock endpoints let you move any customer or order to any status, so you can test every branch of your code in minutes.

## Sandbox vs production

| | Sandbox | Production |
| - | - | - |
| Host | `https://sandbox.zapyd.com` | `https://api.zapyd.com` |
| Credentials | Sandbox key, secret and widget `app_id` | Production key, secret and widget `app_id` |
| KYC | Set it with a mock endpoint | Real checks |
| Payins | Any well-formed transfer reference, then a mock status | Real bank transfers |
| Payouts | Sepolia testnet stablecoins, then a mock status | Mainnet funds |
| Mock endpoints | Available | Not available |

Every request is signed the same way in both environments. The [Quickstart](/guides/getting-started/quickstart) has a request helper that the examples below use.

## Mock endpoints

| Object | Endpoint | Values |
| - | - | - |
| Customer KYC | [`PATCH /cms/api/v1/kyc/mock-kyc-status`](/api-reference-exchange/endpoint/kyc/mock-kyc-status) | `VERIFIED`, `UNVERIFIED`, `DOCUMENT_VERIFICATION_FAILED`, `TAX_VERIFICATION_FAILED`, `ADDITIONAL_INFO_VERIFICATION_FAILED` |
| Bank account | [`POST /cms/api/v1/bank/mock-bank-verification`](/api-reference-exchange/endpoint/bank/mock-bank-verification) | `VERIFIED`, `FAILED` (with a `failure_reason`), `MANUAL_REVIEW` |
| Payin | [`PATCH /pis/api/v1/payin/mock-payin-status`](/api-reference-exchange/endpoint/payin/order/mock-payin-status) | `SUCCESS`, `ON_HOLD`, `REFUNDED`, `FAILED` |
| Payout | [`PATCH /pos/api/v1/payout/mock-payout-status`](/api-reference-exchange/endpoint/payout/order/mock-payout-status) | `SUCCESS`, `FAILED`, `REFUNDED` |
| Payin EDD | [`PATCH /ren/api/v1/payin/edd/mock-status`](/api-reference-exchange/endpoint/payin/edd/edd-mock-status) | `VERIFIED`, `FAILED`, `IN_REVIEW`, `DOCS_REQ`, `PROCESSING` |
| Payout EDD | [`PATCH /ren/api/v1/payout/edd/mock-status`](/api-reference-exchange/endpoint/payout/edd/edd-mock-status) | `VERIFIED`, `FAILED`, `IN_REVIEW`, `DOCS_REQ`, `PROCESSING` |

Each mock fires the same webhook production would send, except the bank account mock: read the account to see its new status.

```javascript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
await zapyd("PATCH", "/cms/api/v1/kyc/mock-kyc-status", { customer_id, kyc_status: "VERIFIED" });
await zapyd("PATCH", "/pis/api/v1/payin/mock-payin-status", { payin_id, payin_status: "ON_HOLD" });
await zapyd("PATCH", "/pos/api/v1/payout/mock-payout-status", { payout_id, payout_status: "REFUNDED" });
```

## Test funds for payouts

Payout quotations on `sepolia` return a testnet `wallet_address`.

1. Get Sepolia ETH for gas and testnet stablecoins from a faucet.
2. Send the exact `sending_amount` to the quotation's `wallet_address`.
3. Initiate with the Sepolia transaction hash, then set the result with the mock endpoint.

<Warning>
  Sepolia works only in sandbox. Never send real funds to a sandbox address.
</Warning>

## Webhooks on your laptop

Zapyd needs a public HTTPS URL. Expose your local server with a tunnel such as [ngrok](https://ngrok.com/), then register it:

```bash theme={"theme":{"light":"css-variables","dark":"css-variables"}}
ngrok http 3000
```

```javascript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
await zapyd("POST", "/org/api/v1/organizations/api-webhooks", {
  webhook_url: "https://<your-subdomain>.ngrok-free.app/webhooks/zapyd",
});
```

## Scenarios to cover

<AccordionGroup>
  <Accordion title="Customers and KYC">
    * Customer create, then the same `client_reference_id` again (it returns the same customer)
    * KYC `VERIFIED`, then each failure value and its update endpoint
    * Bank account `VERIFIED` and `FAILED`
  </Accordion>

  <Accordion title="Payins">
    * Quotation, then initiate before and after `expiry_time`
    * Every final status: `SUCCESS`, `ON_HOLD`, `REFUNDED`, `FAILED`
    * A reused `transaction_reference_id` is rejected
    * Crypto is credited only on `SUCCESS`
  </Accordion>

  <Accordion title="Payouts">
    * Quotation with `sending_amount`, and with `receiving_amount`
    * A reused `transaction_hash` is rejected
    * Every final status: `SUCCESS`, `FAILED`, `REFUNDED`
  </Accordion>

  <Accordion title="Limits and EDD">
    * A limit is exceeded, then EDD is submitted and mocked to `VERIFIED` and `FAILED`
  </Accordion>

  <Accordion title="Errors and webhooks">
    * Wrong signature, expired timestamp and wrong module prefix
    * Duplicate webhook delivery is ignored
    * Your endpoint is down, and the order is recovered by polling
  </Accordion>
</AccordionGroup>

When everything passes, work through the [Go-live checklist](/guides/development-and-testing/go-live).


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