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

# Testing

> Sandbox test data, cards, and scenarios for every Modulus API in one place

## Overview

Every Modulus API has a sandbox that mirrors production behavior with no real money moved. This page is the single reference for sandbox **test data** across all APIs: card numbers, simulation codes, activation codes, and the scenarios each one triggers. Sandbox and production use separate credentials and URLs.

<Note>
  Sandbox keys are provisioned during onboarding. A sandbox key never moves real money. Do not send production keys to the sandbox URLs below.
</Note>

## Sandbox base URLs

| API                                   | Sandbox base URL                                     |
| ------------------------------------- | ---------------------------------------------------- |
| Ecom (Payment Links, Payment Intents) | `https://api.sbx.moduluslabs.io/ecom/v1`             |
| Ecom JavaScript SDK                   | `https://js.sbx.moduluslabs.io/v1`                   |
| QR Ph                                 | `https://qrph.sbx.moduluslabs.io`                    |
| Transaction Reporting                 | `https://api.sbx.moduluslabs.io/reports`             |
| Onboarding                            | `https://kyc.sbx.moduluslabs.io`                     |
| Webhooks                              | `https://webhooks.sbx.moduluslabs.io`                |
| Terminal Gateway                      | `https://api.sbx.moduluslabs.io/v1/terminal-gateway` |

## Ecom: test cards

Use these cards on the [hosted checkout](/docs/ecom/payment-links) (Payment Links) and in the [JavaScript SDK](/docs/ecom/jssdk/introduction). No real money moves.

| Scenario              | Scheme     | Number                | Expiry | CVC | 3DS OTP |
| --------------------- | ---------- | --------------------- | ------ | --- | ------- |
| 3DS challenge (OTP)   | Visa       | `4000 0000 0000 2503` | 12/28  | 123 | `1234`  |
| 3DS challenge (OTP)   | Mastercard | `5200 0000 0000 2151` | 12/28  | 123 | `1234`  |
| Frictionless (no OTP) | Visa       | `4456 5300 0000 1005` | 12/28  | 123 | -       |
| Frictionless (no OTP) | Mastercard | `5200 0000 0000 1005` | 12/28  | 123 | -       |

Challenge cards present a 3D Secure OTP step (enter `1234`). Frictionless cards authenticate with no customer interaction. Any future expiry date and any 3-digit CVC work in sandbox.

## Ecom: scenario matrix

What to run, what you should get back, and what to assert in your integration.

| Scenario           | How to trigger                                                                  | Expected result                                                                                              | Assert                                                                             |
| ------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| Successful payment | Pay with any test card, complete 3DS                                            | JavaScript SDK `confirmPayment` resolves `SUCCEEDED`; a payment link's `successful_payment_count` increments | You read `result.receipt`, or retrieve the link and reconcile on `order_reference` |
| Issuer decline     | Use a card and amount your test issuer declines                                 | `DECLINED`                                                                                                   | You surface `result.userMessage` and allow another card                            |
| 3DS challenge      | Use a challenge card, enter OTP `1234`                                          | Lifecycle `requires_action` then `authenticating` then terminal                                              | Your UI shows the challenge (modal or container) and resumes                       |
| 3DS frictionless   | Use a frictionless card                                                         | Lifecycle `submitting` then `authenticating` then terminal, no `requires_action`                             | No challenge UI is shown                                                           |
| Expiry             | Let a link or intent pass `expires_at` / its payment window                     | Payment attempt returns `EXPIRED`                                                                            | You create a fresh link/intent and retry                                           |
| Idempotency replay | Repeat `POST /payment-links` with the same `Idempotency-Key` and identical body | Original response returned, no duplicate created                                                             | Same `id` returned; a changed body returns `409 IDEMPOTENCY_MISMATCH`              |
| Tenant isolation   | Read another entity's link id with your key                                     | `404 NOT_FOUND` (not `403`)                                                                                  | You treat cross-tenant access as not-found, with no existence leak                 |

See [Errors](/docs/errors) for the full code catalog and [3D Secure](/docs/ecom/jssdk/3d-secure) for the challenge flow.

## QR Ph: simulation

QR Ph has no card data. You generate a QR code, then drive outcomes with the Simulate Webhook API using a `useCase` value. The full step-by-step walkthrough (create, convert, decode, simulate, receive) lives in the [QR testing guide](/docs/qr/testing).

**Sandbox activation code:** `A9X4-B7P2-Q6Z8-M3L5`

| `useCase`                         | Decrypted webhook                                    |
| --------------------------------- | ---------------------------------------------------- |
| `SUCCESS`                         | `webhookAction: QRPH_SUCCESS`                        |
| `MISSING_DESTINATION_ACCOUNT`     | `webhookAction: QRPH_DECLINED` (with `failedReason`) |
| `MISSING_PARTNER_REVENUE_ACCOUNT` | `webhookAction: QRPH_DECLINED` (with `failedReason`) |
| `UNSUPPORTED_TRANSFER_TYPE`       | `webhookAction: QRPH_DECLINED` (with `failedReason`) |

Reusing the same `merchantReferenceNumber` is rejected as a duplicate. See the [QR testing guide](/docs/qr/testing) for the complete simulation flow and additional edge cases.

## Transaction Reporting, Onboarding, Terminal Gateway

These APIs have no discrete card or simulation fixtures. Test them against the sandbox base URLs above using **each API's own auth** - Transaction Reporting uses `X-API-Key`, Onboarding uses a Bearer JWT, and Terminal Gateway uses HMAC-signed headers (see each API's Authentication page). Each endpoint's reference page includes a complete sample request and response you can run directly:

<CardGroup cols={2}>
  <Card title="Transaction Reporting" icon="chart-line" href="/docs/transaction-reporting/quickstart">
    List merchants and transactions with a sandbox key
  </Card>

  <Card title="Onboarding" icon="user-plus" href="/docs/onboarding/introduction">
    Onboard test merchants against the sandbox
  </Card>

  <Card title="Terminal Gateway" icon="credit-card" href="/docs/terminal-gateway/introduction">
    Exercise terminal flows in sandbox
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/docs/errors">
    Every error code, cause, and fix
  </Card>
</CardGroup>
