---
url: /sandbox-environment.md
---
# Sandbox environment

Build and validate your integration in the isolated **sandbox environment** before going live. No real money moves, no real settlements occur, and sandbox accounts never appear in the live merchant portal.

## Sandbox base URL

```
https://sandbox-api.maytes.co
```

Use the **sandbox** `client_id` and `client_secret` from the merchant portal with this host. The API shape is identical to production; only the host and credentials change. See [Environments](/authentication#environments).

## Test cards

Use these card numbers in the sandbox with any future expiry date, any 3-digit CVC, and any postal code. **Stripe** processes payments in the Maytes window.

### Successful payments

| Card type | Number | Result |
|---|---|---|
| Visa | `4242 4242 4242 4242` | Charge succeeds. |
| Visa (debit) | `4000 0566 5566 5556` | Charge succeeds. |
| Mastercard | `5555 5555 5555 4444` | Charge succeeds. |
| American Express | `3782 822463 10005` | Charge succeeds. |

### Declines

| Card type | Number | Result |
|---|---|---|
| Visa | `4000 0000 0000 0002` | Generic decline. |
| Visa | `4000 0000 0000 9995` | Insufficient funds. |
| Visa | `4000 0000 0000 9987` | Lost card. |
| Visa | `4000 0000 0000 9979` | Stolen card. |
| Visa | `4000 0000 0000 0069` | Expired card. |
| Visa | `4000 0000 0000 0127` | Incorrect CVC. |
| Visa | `4000 0000 0000 0119` | Processing error. |

Use these cards to test error handling. A declined checkout transitions to `failed`; don't fulfill the order.

### 3-D Secure authentication

| Card type | Number | Behavior |
|---|---|---|
| Visa | `4000 0027 6000 3184` | Requires 3DS authentication; succeeds when the customer confirms. |
| Visa | `4000 0082 6000 3178` | Requires 3DS authentication; fails when the customer confirms. |

The customer sees Stripe's 3DS challenge inside the Maytes window. Approve or decline it to test both paths.

See [Stripe's docs](https://stripe.com/docs/testing) for international cards, fraud-blocked numbers, AVS variations, and the full test-card catalog.

## Testing webhooks

Make your webhook endpoint publicly reachable over HTTPS. For local development, put a tunnel such as ngrok or cloudflared in front of your server and subscribe the tunnel URL.

1. **Subscribe** your sandbox endpoint (portal, or the [subscription API](/webhooks#managing-your-subscription)) and store the signing secret it returns.
2. **Send a `webhook.test` event** from the portal. It bypasses your event-type filter and always arrives. Confirm [signature verification](/webhooks#verify-the-signature) passes and you respond 2xx.
3. **Check the delivery log** in the portal or through `GET /api/merchant/v1/webhooks/deliveries`. Every attempt records the HTTP status your endpoint returned.

## What to test before going live

1. **Happy path** — create a checkout, complete it with `4242 4242 4242 4242`, confirm your `checkout.authorized` handler fires, captures, and the checkout reaches `captured`.
2. **Signature rejection** — send a request to your webhook endpoint with a bad signature. Confirm you reject it with a 4xx and don't process it.
3. **Duplicate delivery** — replay the same event (same envelope `id`) at your handler. Confirm you don't fulfill twice.
4. **Decline** — pay with `4000 0000 0000 0002`. Confirm your code reacts to the `failed` status and doesn't fulfill the order.
5. **Abandonment** — start a checkout, close the Maytes window. Confirm the customer lands on your `cancel_url` and your order stays in the right state.
6. **Cancel** — for a checkout that authorized but you don't want to capture, call `/cancel`. Confirm the status reaches `voided` and you receive `checkout.voided` with `reason: "merchant_cancel"`.
7. **Amount mismatch** — send a `total_amount` that doesn't reconcile with your line items. Confirm you handle `422 invalid_amount_arithmetic`.
8. **Order-already-authorized** — retry your create call for the same `merchant_order_id`. Confirm you handle `409 order_already_authorized` by reusing the existing `checkout_uuid` instead of recreating.

## Sandbox data is isolated

* **No real money moves.** Stripe in test mode never charges a real card; settlements never reach a bank account.
* **Separate accounts.** Customer accounts created in the sandbox only exist in the sandbox.
* **Separate portal.** Your sandbox merchant portal is separate from your live one.

To go live, use the production credentials from the portal, send requests to **`https://api.maytes.co`**, and create a **production webhook subscription** with its own signing secret. Request shapes, status lifecycles, and error codes remain identical.

## Next

* [Quickstart](/quickstart) — the webhook flow end to end, against this environment.
* [Webhooks](/webhooks) — signature verification and delivery semantics.
* [Authentication](/authentication) — how to mint a Bearer token from your credentials.
* [API reference](/api-reference) — the full schema against the sandbox.
