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

# Test mode

> Use test mode to develop and test your integration without transferring real assets.

MoonPay provides a test mode for integration development and testing. Test mode uses testnet blockchains and simulated payments. No real assets are transferred.

## Enabling test mode

Test mode is determined by the API key you use when [creating a session](/api-reference/platform/endpoints/sessions/create). Use your **test API key** (`sk_test_...`) to enable test mode. Use your **live API key** (`sk_live_...`) for production.

You can find your API keys on the [Developers page](https://dashboard.moonpay.com/developers) in your MoonPay dashboard.

<Info>
  All frames and SDK methods automatically operate in test mode when initialized
  with a session token created using test API keys.
</Info>

## Test accounts

When creating test accounts:

* **MoonPay simulates KYC verification** - it does not verify documents. To force a specific approve or reject outcome, see [First names (KYC outcome)](#first-names-kyc-outcome)
* **Use a US or UK address** for test accounts, as these work best with test payment cards
* You can skip document submission by clicking "Skip document submission" if prompted

## Test data

Test mode reserves a set of test values that produce deterministic outcomes. Use them to run the full sign-up and verification journey without a real inbox, a real phone, or a real identity check.

| Value | Where to provide it | What it does |
| :- | :- | :- |
| `test-<label>@moonpay.test` | `email` on [session create](/api-reference/platform/endpoints/sessions/create), or entered by the customer in the auth frame | Verifies with the code `123456` |
| `+1500555000X` | `phoneNumber` on session create or the auth frame, and `phoneNumber.number` on [`PATCH /platform/v1/customers/{id}/kyc`](/api-reference/platform/endpoints/customers/submit-kyc) | Verifies with the code `123456` or simulates a failure |
| `Test-Approve` | `firstName` in `basicDetails` on `PATCH /platform/v1/customers/{id}/kyc` | Approves identity verification |
| `Test-Reject` | `firstName` in `basicDetails` on `PATCH /platform/v1/customers/{id}/kyc` | Rejects identity verification |
| Any 9-digit value | `taxIdentifiers[]` (type `ssn`) on `PATCH /platform/v1/customers/{id}/kyc` | Not validated |

Test email addresses and test first names are case-insensitive.

None of these values work in live mode:

* **Test email addresses** are rejected with a `400` error.
* **Test first names** are treated as ordinary names, and a real identity check runs.
* **Test phone numbers** are not simulated. MoonPay passes them to its SMS provider, which does not support them for verification, so verification fails. Use a real number in live mode.

### Email addresses

Sign in with a MoonPay test email address of the form `test-<label>@moonpay.test`, where `<label>` is any combination of letters, numbers, hyphens, and underscores. For example, `test-signup-flow@moonpay.test`. Every test address verifies with the code `123456`.

MoonPay sends no email to these addresses. Each unique address creates a separate customer, so you can generate as many test customers as you need. As with real addresses, you cannot reuse one address across two customers.

To exercise real email delivery instead, use an inbox you can access. MoonPay sends a one-time passcode (OTP) to the address for login verification.

### Phone numbers

Verify with MoonPay's reserved test phone numbers. These are not real phone numbers, and MoonPay sends no SMS to them. Every test number follows the format `+1500555000X`, where the final digit selects the outcome.

#### Verification succeeds

All three verify with the code `123456`.

| Phone number | Simulated line-type lookup |
| :- | :- |
| `+15005550000`, `+15005550006` | Mobile |
| `+15005550005` | Lookup unavailable, so MoonPay accepts the number instead of blocking it |

#### MoonPay rejects the number

MoonPay rejects these wherever it validates a phone number. On [`PATCH /platform/v1/customers/{id}/kyc`](/api-reference/platform/endpoints/customers/submit-kyc), the request fails with `400` and an `invalid_request` error on the `phoneNumber.number` field, carrying the message below.

| Phone number | Simulated reason | Error message |
| :- | :- | :- |
| `+15005550001` | Lookup returns no line type | `Phone number type is not accepted` |
| `+15005550002` | Landline | `Phone number type is not accepted` |
| `+15005550003` | VoIP | `Phone number type is not accepted` |
| `+15005550007` | Number fails validation (too short) | `Invalid phone number` |

Only mobile numbers are accepted, so the first three share one error message. Use the specific test number to control which case you are exercising.

#### The OTP send fails

These only occur in the OTP flow. The Customer API does not send an OTP.

| Phone number | Simulated failure |
| :- | :- |
| `+15005550004` | Delivery blocked |
| `+15005550008` | Max send attempts |
| `+15005550009` | Too many requests |

Simulated send failures do not block the number or the customer. Every attempt with one of these numbers returns the same error. You can retry right away, or switch the same customer to a success number.

<Note>
  A real phone number belongs to one customer at a time, so verifying it on a
  new customer removes it from the previous one. Test numbers are exempt: any
  number of test customers can hold the same test number at once.
</Note>

### First names (KYC outcome)

Set the customer's **first name** to a test value when completing identity verification. MoonPay returns the outcome you specify instead of running a real identity check.

With the Customer API, submit it in the `basicDetails` requirement on [`PATCH /platform/v1/customers/{id}/kyc`](/api-reference/platform/endpoints/customers/submit-kyc):

```json theme={null}
{ "basicDetails": { "firstName": "Test-Approve" } }
```

| First name | Outcome |
| :- | :- |
| `Test-Approve` | MoonPay approves identity verification |
| `Test-Reject` | MoonPay rejects identity verification |

MoonPay simulates only the verification outcome. Everything else behaves normally, including webhooks, analytics, and transaction gating. `Test-Approve` also approves any submitted documents and any pending SSN check. `Test-Reject` rejects the session.

Test first names work for both Express Checkout and full KYC sessions.

### SSN (US residents)

Enter any 9-digit value. Test mode does not verify SSN values.

The format is enforced in both modes: submit nine digits with no hyphens or spaces. Any other length, or a value containing `-`, fails with a `400` and an `invalid_request` error on `taxIdentifiers.value`.

<Warning>
  Do not use your real Social Security number. Use a fake value like
  `123456789`.
</Warning>

## Test payment cards

Use the following test cards to simulate payments. **Do not enter real payment card information.** Use any valid 3-digit CVV and any future expiration date.

### US customers

| Card type | Card number | Expiration | CVV |
| :- | :- | :- | :- |
| Visa Credit | `4000 0200 0000 0000` | `12/2030` | `100` |

### UK customers

| Card type | Card number | Expiration | CVV |
| :- | :- | :- | :- |
| Visa Credit | `4242 4242 4242 4242` | `12/2030` | `100` |
| Visa Debit | `4659 1055 6905 1157` | `12/2030` | `100` |

### EU customers

| Card type | Card number | Expiration | CVV |
| :- | :- | :- | :- |
| Mastercard Debit (DE) | `5305 4847 4880 0098` | `12/2030` | `100` |

### 3D Secure test cards

3D Secure (3DS) is an additional verification step the customer's bank may require during a card payment. The cards below trigger deterministic 3DS outcomes so you can test your [challenge handling](/platform/guides/handling-challenges) end to end.

* **Frictionless** outcomes complete without any customer interaction.
* **Challenge** outcomes render a 3DS challenge frame the customer must complete or cancel.

<Note>
  When a card triggers a 3DS challenge, the simulator prompts for a password.
  Enter `Checkout1!` to complete the challenge.
</Note>

#### Frictionless

| Outcome | Region | Card type | Card number | Expiration | CVV |
| :- | :- | :- | :- | :- | :- |
| Succeeded | US | Visa Credit | `4485 0403 7153 6584` | `12/2030` | `100` |
| Succeeded | EU (FR) | Visa Credit | `4010 0562 0000 0018` | `12/2030` | `100` |
| Succeeded | EU (FR) | Mastercard Credit | `5137 2100 0000 0018` | `12/2030` | `100` |
| Failed | EU (FR) | Visa Credit | `4022 0501 0000 0000` | `12/2030` | `100` |
| Failed | EU (FR) | Mastercard Credit | `5132 5626 0000 0029` | `12/2030` | `100` |

#### Challenge

| Outcome | Region | Card type | Card number | Expiration | CVV |
| :- | :- | :- | :- | :- | :- |
| Succeeded | US | Mastercard Credit | `5385 3083 6013 5181` | `12/2030` | `100` |
| Succeeded | EU (FR) | Visa Debit | `4010 0617 0000 0021` | `12/2030` | `100` |
| Succeeded | EU (FR) | Mastercard Credit | `5137 2100 0000 0158` | `12/2030` | `100` |
| Failed | US | Visa Credit | `4243 7542 7170 0719` | `12/2030` | `100` |
| Failed | EU (FR) | Visa Credit | `4150 5610 0000 0027` | `12/2030` | `100` |
| Failed | EU (FR) | Mastercard Credit | `5341 0348 0000 0024` | `12/2030` | `100` |

### Declined transactions

Use these cards to test error handling for different failure scenarios.

| Card number | Expiration | CVV | Decline reason |
| :- | :- | :- | :- |
| `4544 2491 6767 3670` | `12/2030` | `100` | Insufficient funds |
| `4897 4535 6848 5113` | `12/2030` | `100` | Suspected fraud |
| `4818 9242 5013 1070` | `12/2030` | `100` | Restricted card |
| `4556 2537 5271 2245` | `12/2030` | `100` | Security violation |
| `4095 2548 0264 2505` | `12/2030` | `100` | Timeout / Internal error |
| `5437 8211 3539 9682` | `12/2030` | `100` | Insufficient funds |
| `5279 9884 0539 8834` | `12/2030` | `100` | Restricted card |
| `5265 1622 7058 7964` | `12/2030` | `100` | Timeout / Internal error |
| `5363 4501 8040 2239` | `12/2030` | `100` | Lost card |

## Apple Pay

In test mode, the Apple Pay frame renders a mock Apple Pay button instead of the native Apple Pay UI. When a customer taps the mock button, a browser confirmation dialog (`window.confirm`) appears in place of the Apple Pay payment sheet:

* **Ok** simulates a successful transaction.
* **Cancel** simulates a failed transaction.

This lets you test the full Apple Pay flow without a real Apple Pay account or Safari-specific setup.

<Note>
  If you embed the Apple Pay frame in an iframe with a `sandbox` attribute,
  include `allow-modals` in the sandbox value. This allows `window.confirm` to
  work cross-origin inside the frame. See the [Apple Pay frame
  reference](/platform/frames/apple-pay#permissions) for details.
</Note>

## Google Pay

In test mode, the Google Pay frame renders a mock Google Pay button instead of the native Google Pay sheet. When a customer taps the mock button, a browser prompt (`window.prompt`) appears in place of the Google Pay payment sheet:

* **OK** authorizes the payment using the billing address in the prompt.
* **Cancel** declines the payment.

The prompt is pre-filled with a US billing address you can edit as JSON before confirming:

```json theme={null}
{
  "name": "John Doe",
  "address1": "123 Main St",
  "address2": "",
  "address3": "",
  "locality": "San Francisco",
  "administrativeArea": "CA",
  "countryCode": "US",
  "postalCode": "94105",
  "phoneNumber": "+14155551234"
}
```

Keep `phoneNumber` in E.164 format (for example `+14155551234`). Invalid JSON in the prompt falls back to this default address.

This lets you test the full Google Pay frame flow without a Google Pay account or the native payment sheet.

The mock applies only when the frame uses this prompt. MoonPay treats the resulting token as a test-mode mock (`authorized` or `declined`). A real Google Pay payment token in test mode still goes to the sandbox payment processor.

<Note>
  If you embed the Google Pay frame in an iframe with a `sandbox` attribute,
  include `allow-modals` in the sandbox value. This allows `window.prompt` to
  work cross-origin inside the frame. See the [Google Pay frame
  reference](/platform/frames/google-pay#permissions) for details.
</Note>

## Triggering challenges

In test mode, you can force a [challenge](/platform/guides/handling-challenges) by setting the buy amount to a specific value. This lets you test how your integration renders and resolves a challenge without reproducing the real-world conditions that normally trigger one.

| Buy amount | Challenge | Payment method |
| :- | :- | :- |
| `48` | Wallet ownership | Apple Pay and card |
| `49` | CVV re-entry | Card |

The amount must match exactly. For example, a quote for `49` triggers the CVV challenge, but `49.01` does not. These triggers only apply in test mode and have no effect with a live API key.

<Note>
  Challenges are surfaced by a `challenge` event that points to the [challenge
  frame](/platform/frames/challenge). See [Handle
  challenges](/platform/guides/handling-challenges) for how to render and
  resolve one.
</Note>

## Bank transfers

Bank-transfer buy transactions (`sepa` for EUR) settle asynchronously. In production, the transaction settles when the customer's deposit arrives and is confirmed. That confirmation never happens in test mode, so a `pending` bank-transfer transaction cannot complete on its own without sending real money.

Use the [simulate bank-transfer settlement](/api-reference/platform/endpoints/transactions/simulate-bank-transfer) endpoint to drive a bank-transfer transaction to a terminal state without real funds.

### Prerequisites

* The transaction is a bank transfer with `paymentMethod.type` set to `sepa` and is in `pending`.
* The request uses the access token of the transaction's own customer. Another customer's transaction returns `404`.
* Test mode only. A live-mode transaction returns `403`.

Open-banking bank transfers settle through a different flow and are not supported by the simulator.

### Outcomes

Set the required `outcome` field to one of the following:

| Outcome | What it simulates | Result |
| :- | :- | :- |
| `settled` | The customer's deposit arrives and matches | The transaction proceeds to completion. |
| `timeout` | The customer never pays | The transaction fails with a bank-transfer timeout, the same as a real 7-day timeout. |

### Poll for the terminal state

Settlement is asynchronous, so the response reflects the transaction's current state rather than its terminal state. Poll [`getTransaction`](/platform/sdk-reference/web/get-transaction) or `GET /platform/v1/transactions/{id}` for the outcome. The `timeout` outcome is immediate; `settled` completes after the simulated deposit is processed.

### Model a specific sender (optional)

The simulator synthesizes sender bank details per the transaction's currency. To model a specific incoming deposit, pass a `senderBankAccount` object with an `accountHolderName` and the account identifiers for the currency:

* `sepa` (EUR): provide an `iban`.

```json Simulate a settled SEPA deposit theme={null}
{
  "outcome": "settled",
  "senderBankAccount": {
    "accountHolderName": "Ada Lovelace",
    "iban": "DE89370400440532013000"
  }
}
```

See the [simulate bank-transfer settlement](/api-reference/platform/endpoints/transactions/simulate-bank-transfer) reference for the full request and response shape.

## Test assets

Test mode supports the following assets and testnets:

| Asset | Code | Testnet | Notes |
| :- | :- | :- | :- |
| Bitcoin | `BTC` | Testnet3 | |
| Bitcoin Cash | `BCH` | Testnet | |
| Ethereum | `ETH` | Sepolia | |
| ERC-20 tokens | `USDC`, `LINK`, `PYUSD`, `RLUSD` | Sepolia | Transfers deliver MoonPay's test token, [MoonPayToken](https://sepolia.etherscan.io/address/0x699cfe8997d647d03325ef4bfd039d5bb0984a17), not the real token. |
| Litecoin | `LTC` | Testnet | |
| Solana | `SOL` | Devnet | Native `SOL` only; SPL tokens are not available in test mode. |
| Stellar | `XLM` | Testnet | |
| TON | `TON` | Testnet | |
| XRP Ledger | `XRP`, `RLUSD` | Testnet | |

<Info>
  This table helps you plan your integration; it is not a contract. The set of
  assets available in test mode shifts over time.
</Info>

Test-mode purchases deliver 1/100th of the quoted amount because MoonPay holds limited testnet funds. A purchase quoted at 0.1 ETH delivers 0.001 ETH to your test wallet.

[Quote requests](/api-reference/platform/endpoints/quotes/get) for assets that aren't available in test mode fail with a `400 invalid_request` error. Use an asset such as `SOL` or `ETH` when testing.

## Troubleshooting

### Common errors

| Error | Cause | Solution |
| :- | :- | :- |
| `Framing 'https://platform.moonpay.com' violates "frame-ancestors" CSP` | Your app or website domain has not been allowlisted | Add domains at [https://dashboard.moonpay.com/developers](https://dashboard.moonpay.com/developers) |
