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

# Sandbox testing

> Create an isolated sandbox account and test quotes, checkout, transaction updates, and webhooks without moving real funds.

The Orki sandbox is a self-contained environment for integration testing. Its accounts, API keys, transactions, webhook configuration, and logs are separate from production.

| Surface     | Sandbox URL                                              |
| ----------- | -------------------------------------------------------- |
| Terminal    | [https://demo.money.orki.io](https://demo.money.orki.io) |
| Partner API | `https://sandbox.money.orki.io`                          |

<Warning>
  Never send a sandbox-created API key to `api.money.orki.io`, and never send a production-created API key to `sandbox.money.orki.io`. A key is valid only in the environment where it was created.
</Warning>

## Set up a sandbox account

<Steps>
  <Step title="Create a separate sandbox account">
    Open [demo.money.orki.io](https://demo.money.orki.io) and register. Production accounts are not copied into the sandbox, so create an account there even if you already use the production Terminal.
  </Step>

  <Step title="Optionally test sandbox KYB">
    The Sumsub sandbox KYB flow is available for testing onboarding, but completing it is not required to request quotes, create Partner API transactions, receive provider completion callbacks, or receive outgoing partner webhooks. Sandbox KYB approval does not approve the corresponding production account.
  </Step>

  <Step title="Start a plan or free trial">
    Start a subscription or trial from the sandbox Terminal. All subscribed sandbox plans can use the Partner API and access the configured sandbox providers.
  </Step>

  <Step title="Create the sandbox API key">
    Open **API Management**, create an API key, and copy its plaintext value when it is displayed. The plaintext key cannot be recovered later; revoke it and create another key if it is lost.
  </Step>
</Steps>

## Confirm the API key

Use the key with the sandbox base URL and the `X-ORKI-API-KEY` header:

```bash theme={null}
curl --get "https://sandbox.money.orki.io/api/v1/partner/fiat-currencies" \
  --header "X-ORKI-API-KEY: YOUR_SANDBOX_API_KEY" \
  --data-urlencode "type=BUY"
```

A `200` response confirms that the key is recognized. A `401` response usually means the key is missing, revoked, or was created in the other environment. A `402` response means the account does not have an active subscription or trial. Sandbox Partner API access is not gated by plan tier or Sumsub KYB; a `403` can indicate an account-level restriction such as suspension.

## Test a complete transaction lifecycle

Sandbox capability varies by provider. Some providers support only discovery or checkout creation in their test environment. Stripe currently supports the complete Orki sandbox path: quote, checkout, provider callback, transaction update, and outgoing partner webhook.

Before completing checkout, configure an outgoing webhook as described below. Webhooks are the normal asynchronous completion signal in sandbox, just as they are in production. The transaction retrieval endpoint is available for reconciliation and recovery if a delivery is delayed or missed.

Use this route for a complete BUY test:

| Field            | Value               |
| ---------------- | ------------------- |
| Provider         | `stripe`            |
| Fiat currency    | `EUR`               |
| Cryptocurrency   | `USDC`              |
| Network          | `ethereum`          |
| Payment method   | `credit_debit_card` |
| Transaction type | `BUY`               |

<Warning>
  Explicitly select `stripe` when creating the sandbox transaction, even if Coinify, MoonPay, or another quote is marked `is_best`. The `is_best` flag reflects quote pricing only; it does not describe the provider's sandbox lifecycle coverage.
</Warning>

Use a correctly formatted Ethereum address dedicated to testing so you can also verify `recipient_address` throughout your integration.

### 1. Request quotes

```bash theme={null}
curl --get "https://sandbox.money.orki.io/api/v1/partner/quotes" \
  --header "X-ORKI-API-KEY: YOUR_SANDBOX_API_KEY" \
  --data-urlencode "fiat_currency=EUR" \
  --data-urlencode "crypto_currency=USDC" \
  --data-urlencode "network=ethereum" \
  --data-urlencode "payment_method=credit_debit_card" \
  --data-urlencode "amount=25" \
  --data-urlencode "type=BUY" \
  --data-urlencode "wallet_address=YOUR_VALID_ETHEREUM_ADDRESS"
```

Find the quote whose `provider.identifier` is `stripe`. Provider availability is evaluated at request time, so a route that is normally supported can still be temporarily unavailable.

### 2. Create the transaction

```bash theme={null}
curl --request POST "https://sandbox.money.orki.io/api/v1/partner/transactions" \
  --header "X-ORKI-API-KEY: YOUR_SANDBOX_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "fiat_currency": "EUR",
    "crypto_currency": "USDC",
    "network": "ethereum",
    "payment_method": "credit_debit_card",
    "amount": 25,
    "type": "BUY",
    "provider": "stripe",
    "wallet_address": "YOUR_VALID_ETHEREUM_ADDRESS",
    "partner_context": "sandbox-order-1001",
    "redirect_url": "https://your-app.example/ramp/return"
  }'
```

Store `data.transaction_id`, then open `data.link` in a browser to continue through the provider checkout. Creating the Orki transaction only creates the tracking record and checkout link; it does not complete the purchase.

### 3. Complete the Stripe sandbox checkout

Stripe documents these values for completing its sandbox onramp:

| Checkout field | Sandbox value        |
| -------------- | -------------------- |
| OTP            | `000000`             |
| SSN            | `000000000`          |
| Address line 1 | `address_full_match` |
| Card number    | `4242424242424242`   |

Stripe can override the requested amount with its sandbox limits. See [Stripe's sandbox onramp instructions](https://docs.stripe.com/crypto/onramp/embedded#sandbox-values) for the provider's current requirements.

### 4. Receive the outgoing webhook

The transaction is initially `pending`. After Orki receives and verifies a terminal provider callback, it updates the transaction and delivers the corresponding event to your configured sandbox webhook endpoint.

| Provider outcome                                                    | Orki status | Outgoing event        |
| ------------------------------------------------------------------- | ----------- | --------------------- |
| Stripe reaches `fulfillment_complete`                               | `success`   | `transaction.success` |
| Stripe reaches `rejected`                                           | `failed`    | `transaction.failed`  |
| Checkout is incomplete, abandoned, or remains at `requires_payment` | `pending`   | None                  |

<Note>
  A declined Stripe sandbox payment can remain at `requires_payment`; it does not necessarily produce a terminal failure callback. A deterministic success path is available with Stripe's documented sandbox values, while a final failure event is emitted only when the provider reports a terminal rejected state.
</Note>

### 5. Reconcile through the transaction API

Use the transaction endpoint to confirm the stored state, recover after a missed webhook, or reconcile periodically:

```bash theme={null}
curl --get "https://sandbox.money.orki.io/api/v1/partner/transactions/ORKI_TRANSACTION_ID" \
  --header "X-ORKI-API-KEY: YOUR_SANDBOX_API_KEY"
```

Polling is a fallback and reconciliation mechanism. It does not replace webhook handling for normal transaction updates.

## Test outgoing webhooks

Configure webhooks before completing the provider checkout:

1. Create a public HTTPS endpoint that accepts `POST` requests.
2. In the sandbox Terminal, open **API Management → Webhooks**.
3. Register the endpoint and select success, failure, or both.
4. Save the webhook signing secret securely.
5. Complete the Stripe sandbox checkout and return `2xx` promptly when the event arrives.

Sandbox delivers the same public event names and payload structure as production:

* `transaction.success`
* `transaction.failed`

The value supplied as `partner_context` during transaction creation is returned as `data.partner_context`. Use it to reconcile the webhook with your own order.

Partner webhooks are sent for API transactions and client-owned widget transactions. Transactions created through the public, non-client-owned widget do not generate partner webhooks.

### Verify the signature

Every delivery includes:

* `X-Timestamp`: Unix timestamp used when signing the request.
* `X-Signature`: lowercase hexadecimal HMAC-SHA256 signature.

Read the request body exactly as received and calculate:

```text theme={null}
HMAC-SHA256(webhook_secret, X-Timestamp + "." + raw_request_body)
```

Compare the result with `X-Signature` using a timing-safe comparison. Reject an invalid signature and reject timestamps outside your accepted replay window. See [Webhooks](/webhooks/webhooks) for implementation examples and retry behavior.

Each sandbox account has one active webhook destination and signing secret. Saving a webhook subscription rotates its signing secret, so update your receiver whenever you change and save the subscription. Production webhook configuration is separate because production uses a separate account and environment.

## What sandbox proves

Sandbox can validate:

* API-key authentication and environment isolation.
* Asset, payment-method, and quote discovery.
* Transaction creation and checkout redirects.
* Provider callbacks and final Orki status when the selected provider supports them.
* Outgoing webhook delivery, with transaction retrieval available for fallback reconciliation.
* Webhook payload reconciliation and signature verification.

Sandbox does not prove real fiat settlement, real blockchain delivery, or production KYB approval. Repeat a small controlled transaction in production before enabling wider access.

## Troubleshooting

| Symptom                          | Check                                                                                                                                                     |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401 Invalid or revoked API key` | Confirm the key was created at `demo.money.orki.io` and is being sent to `sandbox.money.orki.io`.                                                         |
| No Stripe quote                  | Confirm the complete EUR/USDC/Ethereum/card combination and use a supported amount and location.                                                          |
| Transaction remains `pending`    | Confirm checkout reached a terminal provider state. Abandoned or declined payment attempts can remain non-terminal.                                       |
| No partner webhook               | Confirm the transaction belongs to the sandbox account, the event is selected, the endpoint is public HTTPS, and the provider issued a terminal callback. |
| Signature mismatch               | Use `X-Timestamp + "." + raw body`; do not parse and re-serialize the JSON before calculating the HMAC.                                                   |
| Webhook returned non-2xx         | Review **API Management → Webhooks** logs. Orki retries automatic deliveries twice with backoff, and the Terminal also supports a manual retry.           |

Continue with [API Integration](/api-integration) for every request and response field, [Events](/webhooks/events) for the payload schema, and [Errors](/api-reference/errors) for API error handling.
