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

# ONRAMP orders (sandbox)

> Step-by-step guide to testing fiat-in → crypto-out ONRAMP orders in sandbox: happy path, failure scenarios, rate-lock expiry, and webhooks

An ONRAMP order converts a customer's fiat deposit into a crypto asset. In sandbox every bank transfer and crypto delivery is synthetic — no real funds move and no chain is involved. The source (fiat-in) and destination (crypto-out) conversion legs are internal movements that the mock provider finalizes automatically. `order.succeeded` fires within seconds of order creation — no manual settle calls are required for the happy path.

Both legs are Conduit-internal movements (there is no external actor delivering fiat or crypto on either side). Because there is no real external actor that can independently fail these legs, there is no mid-flow injection lever for them. Use `orders/:id/simulate/conversion-failed` (below) to drive the whole order to a failed terminal state, or create the pending auto-execute order first and then inject a source fiat deposit whose `senderInfo.accountNumber` carries a failure suffix — see [Deposits](/sandbox/deposits) and [Sandbox overview](/sandbox/overview).

## Prerequisites

* An `active` customer with at least one virtual account for the source fiat asset. Follow [Sandbox quickstart](/sandbox/quickstart) if you haven't set this up yet.
* Your sandbox API key exported as `SANDBOX_API_KEY`.
* The customer's virtual account ID exported as `VA_ID`.

```bash theme={null}
export SANDBOX_API_KEY="ck_sandbox_..."
export CUSTOMER_ID="cus_..."
export VA_ID="vac_..."
```

## Lifecycle overview

An ONRAMP order moves through these stages: the order is created and rate-locked → the source (fiat-in) and destination (crypto-out) conversion legs finalize automatically → `order.succeeded` fires. The conversion sub-leg is covered in detail on [/sandbox/conversions](/sandbox/conversions).

Because both legs are internal movements in sandbox, there are no manual settle calls in the happy path. Use `orders/:id/simulate/conversion-failed` to force the order to a failed terminal state, or create the pending order and then simulate a source fiat deposit with a deposit suffix.

Intermediate state is observable only via `GET /v2/orders/:id` (poll). Terminal status surfaces via webhook: `order.succeeded` or `order.failed` or `order.cancelled`.

## Full happy path

### Step A — Create the order

Create the order by supplying the source virtual account (USD), the destination wallet (USDC), the lock side, and the amount. Set `autoExecute: true` to let the platform begin execution immediately.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.sandbox.conduit.financial/v2/orders \
    -H "x-api-key: $SANDBOX_API_KEY" \
    -H "idempotency-key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "clientReferenceId": "cref-onramp-1",
      "source": { "type": "virtual_account", "id": "'"$VA_ID"'" },
      "destination": {
        "type": "wallet",
        "id": "wlt_<your-wallet-id>",
        "asset": { "code": "USDC", "chain": "ethereum" }
      },
      "lockSide": "source",
      "amount": "100.00",
      "autoExecute": true
    }'
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(`${process.env.SANDBOX_HOST}/v2/orders`, {
    method: "POST",
    headers: {
      "x-api-key": process.env.SANDBOX_API_KEY!,
      "idempotency-key": crypto.randomUUID(),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      clientReferenceId: "cref-onramp-1",
      source: { type: "virtual_account", id: process.env.VA_ID },
      destination: {
        type: "wallet",
        id: "wlt_<your-wallet-id>",
        asset: { code: "USDC", chain: "ethereum" },
      },
      lockSide: "source",
      amount: "100.00",
      autoExecute: true,
    }),
  });
  const { id: orderId } = await response.json();
  // orderId → "ord_..."
  ```

  ```python Python theme={null}
  import httpx, uuid, os

  r = httpx.post(
      f"{os.environ['SANDBOX_HOST']}/v2/orders",
      headers={
          "x-api-key": os.environ["SANDBOX_API_KEY"],
          "idempotency-key": str(uuid.uuid4()),
          "Content-Type": "application/json",
      },
      json={
          "clientReferenceId": "cref-onramp-1",
          "source": {"type": "virtual_account", "id": os.environ["VA_ID"]},
          "destination": {
              "type": "wallet",
              "id": "wlt_<your-wallet-id>",
              "asset": {"code": "USDC", "chain": "ethereum"},
          },
          "lockSide": "source",
          "amount": "100.00",
          "autoExecute": True,
      },
  )
  order_id = r.json()["id"]
  # order_id → "ord_..."
  ```
</CodeGroup>

`202 Accepted`. Capture `id` as `ORDER_ID`. The mock provider auto-finalizes the source (fiat-in) and destination (crypto-out) legs internally. Webhook: `order.succeeded` (status: `succeeded`) fires within seconds — no manual settle calls are needed.

```bash theme={null}
export ORDER_ID="ord_..."
```

Poll `GET /v2/orders/$ORDER_ID` to observe progress if needed. No webhook fires for intermediate leg state — only terminal outcomes emit a webhook.

## Simulate conversion failure

Force the in-flight conversion to fail regardless of which leg it is waiting on. The order terminates in `failed`; any funds already debited from the source are returned automatically.

```bash theme={null}
curl -X POST https://api.sandbox.conduit.financial/v2/sandbox/orders/$ORDER_ID/simulate/conversion-failed \
  -H "x-api-key: $SANDBOX_API_KEY" \
  -H "idempotency-key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Provider rate stale"}'
```

`200 OK` returning the order at its current state. Webhook: `order.failed` carrying `orderId`, `customerId`, `clientReferenceId`, `reasonCode`, `failureMessage` (the `reason` you supplied), and `failedAt`. See [/sandbox/conversions](/sandbox/conversions) for more detail.

## Source-deposit compliance failure

For unfunded ONRAMP tests, create the order with `autoExecute: true`, then inject the fiat source deposit through the deposits simulator with `senderInfo.accountNumber` ending in `95009001` or `95009002`. The deposit freezes with `transaction.failed` and `failureCode: "compliance_hold"`, and the oldest pending auto-execute order that matches the deposit and is amount-covered emits `order.failed` within a few seconds. `orders/:id/simulate/conversion-failed` remains the lever for conversion-level failures after the order has begun executing.

## Simulate rate-lock expiry

Backdates the order's rate-lock timestamp so the next sweep cycle treats the lock as expired. The order transitions to `cancelled` with `cancellationReason: "expired"`.

```bash theme={null}
curl -X POST https://api.sandbox.conduit.financial/v2/sandbox/orders/$ORDER_ID/simulate/rate-lock-expired \
  -H "x-api-key: $SANDBOX_API_KEY" \
  -H "idempotency-key: $(uuidgen)"
```

`200 OK` returning the order at its current state. Webhook: `order.cancelled` with `cancellationReason: "expired"`. Cancellation lands within \~1 second via an immediate background sweep tick. No need to wait for the scheduled 30-second sweep.

<Note>
  This endpoint enqueues an immediate sweep tick so you can verify your integration handles rate-lock expiry without waiting for the live lock window to elapse.
</Note>

## Order status reference

| Status      | Meaning                                                                                              | Possible next statuses             |
| ----------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `pending`   | Order created; rate locked. Execution has not started.                                               | `succeeded`, `failed`, `cancelled` |
| `succeeded` | Source debited, conversion completed, destination credited. Terminal.                                | —                                  |
| `failed`    | Execution failed on the source, conversion, or destination leg. Terminal.                            | —                                  |
| `cancelled` | Order cancelled before execution. `cancellationReason` is `expired` or `client_cancelled`. Terminal. | —                                  |

<Note>
  Intermediate steps (source settled, conversion in progress) are not reflected as order statuses and do not emit webhooks. Poll `GET /v2/orders/{orderId}` for current state; only the terminal outcomes (`succeeded`, `failed`, `cancelled`) surface via webhook.
</Note>

## Webhook events

| Event             | When it fires                                                                                                                                |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `order.succeeded` | Order reached `succeeded`: source debited, destination credited. Carries `transactionId`, `sourceAssetAmount`, `destinationAssetAmount`.     |
| `order.failed`    | Order reached `failed`. `reasonCode` is `insufficient_funds`, `provider_unavailable`, `provider_rejected`, `internal_error`, or `cancelled`. |
| `order.cancelled` | Order cancelled before execution. `cancellationReason` is `expired` or `client_cancelled`.                                                   |

## Errors

Simulate endpoints return `404 ORDER_NOT_FOUND` when the order does not exist or belongs to a different organization. Replays against an already-terminal order return `200` with the order at its current state (idempotent). If execution has not yet started, the simulator arms the failure for when it does. See [/errors](/errors) for the full error shape. The `order.failed` payload carries `reasonCode` (`insufficient_funds` / `provider_unavailable` / `provider_rejected` / `internal_error` / `cancelled`) describing the failure category.

## Sequence diagram

```mermaid theme={null}
sequenceDiagram
    participant You as Your backend
    participant API as Conduit sandbox
    participant WH as Your webhook endpoint

    You->>API: POST /v2/orders (source=virtual_account, destination=wallet)
    API-->>You: 202 Accepted { id: "ord_..." }

    Note over API: Mock provider auto-finalizes source and destination legs
    Note over You,API: Poll GET /v2/orders/{orderId} to observe progress (optional)

    API-->>WH: order.succeeded (status: succeeded)
```

## Related pages

* [Sandbox quickstart](/sandbox/quickstart) — set up customer, virtual account, and wallet in under 5 minutes
* [OFFRAMP orders](/sandbox/offramps) — crypto-in → fiat-out counterpart
* [Conversions](/sandbox/conversions) — conversion sub-leg reference
* [Sandbox overview](/sandbox/overview) — full sandbox posture and what is synthetic
* [Webhooks reference](/webhooks) — full payload schemas for all `order.*` events
* [Errors](/errors) — RFC 9457 error shape and `failureCode` catalog
