> ## 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.

# Conversions in sandbox

> Test FX conversion failure scenarios inside ONRAMP and OFFRAMP orders using the orders/:id/simulate/conversion-failed endpoint

Every ONRAMP and OFFRAMP order includes an FX conversion leg that exchanges the source asset for the destination asset. The conversion runs automatically after the source leg settles; there is no standalone endpoint to create one. This page explains how to drive conversion failure outcomes in sandbox.

<Note>
  This page applies to both order shapes. An order that names a `source` is funded from a balance the customer already holds; an order created with **no** `source` — sending `sourceAsset` instead — is funded at an address Conduit publishes on the order, via `POST /v2/sandbox/orders/:orderId/deposits/simulate`. Both then run the same conversion leg and fail the same way. See [Deposit-Funded Orders](/concepts/deposit-funded-orders) and the [deposit-funded walkthrough](/sandbox/offramps).
</Note>

<Note>
  The `reason` field is optional (max 500 chars). When supplied, it surfaces verbatim as `failureMessage` on the `order.failed` webhook payload and on the polled `GET /v2/orders/:id` response, with no transformation.
</Note>

## How to fail a conversion

Use the `orders/:id/simulate/conversion-failed` endpoint to force a conversion to a failed terminal state. The endpoint is timing-independent: call it at any point after order creation. If the conversion is already running, it is driven to failed immediately; otherwise the failure is armed and applied as soon as the conversion starts. The order is driven to `failed` and `order.failed` is emitted.

<CodeGroup>
  ```bash curl 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"}'
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(
    `${process.env.SANDBOX_HOST}/v2/sandbox/orders/${orderId}/simulate/conversion-failed`,
    {
      method: "POST",
      headers: {
        "x-api-key": process.env.SANDBOX_API_KEY!,
        "idempotency-key": crypto.randomUUID(),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ reason: "Provider rate stale" }),
    },
  );
  ```

  ```python Python theme={null}
  import httpx, os, uuid
  r = httpx.post(
      f"{os.environ['SANDBOX_HOST']}/v2/sandbox/orders/{order_id}/simulate/conversion-failed",
      headers={
          "x-api-key": os.environ["SANDBOX_API_KEY"],
          "idempotency-key": str(uuid.uuid4()),
      },
      json={"reason": "Provider rate stale"},
  )
  ```
</CodeGroup>

Returns `200 OK` with the order at its current state. Returns `404` if the order is not found. Replays against an already-terminal order return `200` with the current resource (idempotent).

<Note>
  **Rate-lock expiry SLA.** `POST
      /v2/sandbox/orders/:id/simulate/rate-lock-expired` returns `200 OK` with the
  order at its current state and the order reaches `cancelled (expired)` within
  about 3 seconds via an immediate background sweep tick. No polling backoff
  needed; no need to wait for the scheduled 30-second sweep.
</Note>

There is no create-time outcome locking for conversions. The only way to force a conversion failure in sandbox is this endpoint, which works at any point after the order exists.

## Webhook events

Conversion outcomes surface on the order's webhook topic. There is no standalone conversion webhook.

| Event             | When it fires                                                                                                                                                                                       |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `order.succeeded` | The full order completed successfully (source settled, conversion ran, destination credited). Terminal.                                                                                             |
| `order.failed`    | The order failed. `reasonCode` (`insufficient_funds` / `provider_unavailable` / `provider_rejected` / `internal_error` / `cancelled`) names the failure category. Conversion failures surface here. |
| `order.cancelled` | The order was cancelled before execution completed. `cancellationReason` is `expired` or `client_cancelled`.                                                                                        |

Intermediate state (conversion in progress, source settled, etc.) is not conveyed via webhooks. Poll `GET /v2/orders/:id` to observe the current order state between terminal events.

See the [Webhooks reference](/webhooks) for full payload schemas.

## Errors

Conversion failures produce `order.failed` on the order. The payload carries `reasonCode` (`insufficient_funds` / `provider_unavailable` / `provider_rejected` / `internal_error` / `cancelled`) describing the failure category. See [Error codes](/errors) for the broader catalog used by other event types.

## See also

* [Sandbox overview](/sandbox/overview)
* [Webhooks reference](/webhooks)
* [Error codes](/errors)
* [Onramps in sandbox](/sandbox/onramps)
* [Offramps in sandbox](/sandbox/offramps)
* [Deposit-funded orders](/concepts/deposit-funded-orders)
