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

# Fiat Onramp

> Accept Lightning payments from Cash App or Strike and deliver USDB, BTC, or stablecoins to any chain.

Many popular apps support zero-fee Lightning payments. Orchestra combines zero-fee Lightning with its Lightning-to-Spark pipeline to create a fiat-to-crypto onramp with a single API call.

The native destination is USDB on Spark ([details](/usdb/overview)). The onramp to USDB skips all bridging and settles in seconds.

Direct BTC destinations on Spark and Bitcoin L1 keep the flow in BTC and charge the platform fee in sats before delivery.

Other destinations (USDC, PathUSD, USDT, ETH, SOL, WBTC, cbBTC on any supported chain) work through the same pipeline but add swap, bridge, and delivery steps.

<Info>
  Try it live at [orchestra.flashnet.xyz/onramp](https://orchestra.flashnet.xyz/onramp).
</Info>

<Warning>
  The fiat onramp is not available to residents of New York City.
</Warning>

## How it works

1. Your app calls `POST /v1/orchestration/onramp` with the destination chain, asset, recipient address, and an amount. The amount can be:
   * BTC sats the user sends (`amount` with `amountMode: "exact_in"`, the default)
   * Destination asset to receive (`amount` with `amountMode: "exact_out"`)
   * USD the user sees in Cash App (`amountFiatUsd`, runs as `exact_in` after Orchestra fetches BTC/USD spot).
2. Orchestra creates a Lightning invoice, builds the order, and returns payment deeplinks.
3. The user opens the deeplink (or scans a QR code) and pays via their preferred Lightning-compatible app.
4. Orchestra receives the Lightning payment, swaps BTC to the destination asset, and delivers it.

The onramp endpoint handles quoting and submission in one call. A separate `/estimate` endpoint exists as an optional preview step (see [Integration](#integration)).

## Why choose USDB or direct BTC?

Every onramp route starts from `lightning:BTC`. When you choose USDB, Orchestra swaps BTC to USDB and finishes on Spark. When you choose BTC on Spark or Bitcoin L1, Orchestra stays in BTC and delivers the remaining sats after fees. When you choose USDC on another chain, Orchestra must also bridge and deliver, adding cost and latency.

|                | USDB on Spark         | BTC on Spark / Bitcoin L1       | USDC on Solana/Base                                                                          |
| -------------- | --------------------- | ------------------------------- | -------------------------------------------------------------------------------------------- |
| Platform fee   | Custom pricing        | Custom pricing                  | Custom pricing                                                                               |
| Fee asset      | USDB                  | BTC                             | USDC                                                                                         |
| Pipeline shape | Verify, swap          | Verify, settle BTC fee, deliver | Verify, swap, bridge, deliver                                                                |
| Delivery time  | Seconds               | Seconds                         | Seconds                                                                                      |
| Sweep fee      | None                  | None                            | May apply ([details](/products/orchestration/order-lifecycle#what-are-the-effective-limits)) |
| Rewards        | 3.5-6% rewards in BTC | None                            | None                                                                                         |

See [USDB Rewards](/usdb/rewards) for reward details.

## What are the fees?

Many Lightning-compatible apps charge zero fees on Lightning payments. Orchestra uses route-and-volume-based pricing that undercuts card-based and bank-transfer onramps. No intermediary fees and no card processing fees. The `/estimate` endpoint returns the exact fee for each route.

<Info>
  For `lightning:BTC -> bitcoin:BTC`, the platform fee is taken in BTC and the final on-chain withdrawal also pays the network withdrawal fee quoted by Spark at delivery time. That Bitcoin withdrawal fee is not Flashnet revenue.
</Info>

## What are the Lightning payment limits?

Some Lightning-compatible apps impose their own limits on Lightning payments (for example, up to \$999 per rolling 7-day window). Check your payment app's documentation for specific limits.

<Warning>
  Validate amounts client-side against your payment app's limits before calling the API.
</Warning>

## Which routes are supported?

The source is always `lightning:BTC`.

**Native (fastest settlement):**

* `lightning:BTC -> spark:USDB`
* `lightning:BTC -> spark:BTC`
* `lightning:BTC -> bitcoin:BTC`

**Bridged destinations:**

* `lightning:BTC -> solana:USDC`
* `lightning:BTC -> base:USDC`
* `lightning:BTC -> base:USDT`
* `lightning:BTC -> base:ETH`
* `lightning:BTC -> solana:SOL`
* `lightning:BTC -> ethereum:USDC`
* `lightning:BTC -> arbitrum:USDC`
* `lightning:BTC -> tempo:USDC`
* `lightning:BTC -> tempo:PathUSD`
* `lightning:BTC -> tron:USDT`
* `lightning:BTC -> plasma:USDT`
* `lightning:BTC -> ethereum:WBTC`
* `lightning:BTC -> base:cbBTC`
* `lightning:BTC -> solana:cbBTC`
* `lightning:BTC -> monad:USDC`
* `lightning:BTC -> monad:cbBTC`
* Many more routes...

## Integration

<Steps>
  <Step title="Get a price estimate">
    Use the estimate endpoint to show the user what they'll receive before committing.

    ```bash theme={null}
    curl 'https://orchestration.flashnet.xyz/v1/orchestration/estimate?\
    sourceChain=lightning&sourceAsset=BTC&\
    destinationChain=spark&destinationAsset=USDB&\
    amount=100000'
    ```

    Response:

    ```json theme={null}
    {
      "estimatedOut": "96543210",
      "feeAmount": "19308",
      "feeBps": 20,
      "feeAsset": "USDB"
    }
    ```
  </Step>

  <Step title="Create the onramp order">
    A single call creates the Lightning invoice, builds the order, and returns payment deeplinks.

    ```ts theme={null}
    const BASE_URL = 'https://orchestration.flashnet.xyz';

    const onramp = await fetch(`${BASE_URL}/v1/orchestration/onramp`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.FLASHNET_API_KEY}`,
        'X-Idempotency-Key': `onramp:${Date.now()}`,
      },
      body: JSON.stringify({
        destinationChain: 'spark',
        destinationAsset: 'USDB',
        recipientAddress: 'spark1YourSparkAddress...',
        amount: '100000', // sats
        slippageBps: 50,
      }),
    }).then((r) => r.json());
    ```

    Response:

    ```json theme={null}
    {
      "orderId": "ord_abc123",
      "quoteId": "q_xyz789",
      "depositAddress": "lnbc1000n1pj...",
      "paymentLinks": {
        "cashApp": "https://cash.app/launch/lightning/lnbc1000n1pj...",
        "shortUrl": "https://orchestration.flashnet.xyz/pay/a3xKm2Rq"
      },
      "amountIn": "100000",
      "estimatedOut": "96543210",
      "feeAmount": "19308",
      "feeBps": 20,
      "totalFeeAmount": "19308",
      "feeAsset": "USDB",
      "route": [
        "BTC",
        "USDB"
      ],
      "expiresAt": "2026-01-15T12:04:00.000Z"
    }
    ```
  </Step>

  <Step title="Complete the payment">
    On mobile, redirect the user to the payment deeplink. On desktop, display the deeplink as a QR code for the user to scan with their phone. Use `shortUrl` when sharing payment links in messages or emails.

    ```ts theme={null}
    // Mobile: direct deeplink
    window.location.href = onramp.paymentLinks.cashApp;

    // Desktop: render QR code with the deeplink URL
    // The QR encodes the full cash.app URL, not the BOLT11 invoice
    renderQR(onramp.paymentLinks.cashApp);

    // Sharing: use the short URL for texts, emails, etc.
    const shareableLink = onramp.paymentLinks.shortUrl;
    // e.g. https://orchestration.flashnet.xyz/pay/a3xKm2Rq
    ```

    The Lightning invoice expires at `expiresAt` (2 minutes from creation). If the user doesn't pay in time, create a new onramp order.
  </Step>

  <Step title="Track the order">
    Use SSE or polling to track progress. USDB orders skip the bridge and delivery steps, so they complete faster.

    ```ts theme={null}
    // SSE (preferred)
    const es = new EventSource(
      `${BASE_URL}/v1/sse/operations/${onramp.orderId}?token=${API_KEY}`
    );
    es.addEventListener('status', (e) => {
      const { status } = JSON.parse(e.data);
      console.log(status); // confirming, swapping, completed, etc.
    });

    // Or poll
    const status = await fetch(
      `${BASE_URL}/v1/orchestration/status?id=${onramp.orderId}`,
      { headers: { Authorization: `Bearer ${API_KEY}` } }
    ).then((r) => r.json());
    ```
  </Step>
</Steps>

## Frontend integration

Onramp shares the browser-side deep link patterns with Pay Links. For the Cash App navigation trick, the SSE order-tracking hook, the API-key proxy pattern, and the pipeline-progress UI, see [Deep link best practices](/products/orchestration/deeplink-best-practices).

## Exact output mode

By default, you specify how many sats the user sends (`exact_in`) and Orchestra estimates the output. With `amountMode: "exact_out"`, you specify the destination amount the recipient should receive, and Orchestra calculates the required Lightning payment.

This is useful when your UX needs to guarantee a specific dollar amount, for example "onramp exactly \$50 USDC."

```ts theme={null}
const onramp = await fetch(`${BASE_URL}/v1/orchestration/onramp`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.FLASHNET_API_KEY}`,
    'X-Idempotency-Key': `onramp:${Date.now()}`,
  },
  body: JSON.stringify({
    destinationChain: 'solana',
    destinationAsset: 'USDC',
    recipientAddress: 'So1anaAddress...',
    amount: '50000000', // 50 USDC (6 decimals)
    amountMode: 'exact_out',
    slippageBps: 50,
  }),
}).then((r) => r.json());
```

The response includes the calculated `amountIn` (sats required) and a Lightning invoice for that amount. The user pays the invoice, and the recipient receives the specified output amount.

For stablecoin destinations, exact-out `amount` must be a whole-cent value in the destination asset's smallest units. For 6-decimal stables, send multiples of `10000`, for example `"50000000"` for \$50.00. If execution produces extra stablecoin units above the exact-out target, Orchestra retains the surplus as `roundingFeeAmount` instead of overdelivering.

<Warning>
  Affiliate fees (`appFees`, `affiliateId`, `affiliateIds`) are not supported with `exact_out`. Use `exact_in` (or `amountFiatUsd`) if you need affiliate fees on the transaction.
</Warning>

<Warning>
  Exact-out is not supported for the direct BTC passthrough routes `lightning:BTC -> spark:BTC` and `lightning:BTC -> bitcoin:BTC`.
</Warning>

## USD-pinned mode (`amountFiatUsd`)

`amountFiatUsd` lets the partner pin the sender's USD figure. Cash App displays the exact dollar amount, the receiver gets the net after fees, and Orchestra runs the order in `exact_in` mode.

The conversion happens server-side at request time: Orchestra fetches the current BTC/USD spot from Coinbase, computes the equivalent sats, and pins that as the source amount. The spot used is recorded on the order and surfaced on the webhook as `spotUsdPerBtc`.

```ts theme={null}
const onramp = await fetch(`${BASE_URL}/v1/orchestration/onramp`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.FLASHNET_API_KEY}`,
    'X-Idempotency-Key': `onramp:${Date.now()}`,
  },
  body: JSON.stringify({
    destinationChain: 'solana',
    destinationAsset: 'USDC',
    recipientAddress: 'So1anaAddress...',
    amountFiatUsd: '100.00', // sender pays exactly $100 in Cash App
  }),
}).then((r) => r.json());
```

The order webhook for the resulting completed order includes the fiat snapshot at the top level:

```json theme={null}
{
  "amountIn": "<sats>",
  "amountOut": "99500000",
  "feeBps": 50,
  "feeAmount": "500000",
  "amountFiatUsd": "100.00",
  "amountFiatCurrency": "USD",
  "spotUsdPerBtc": "100450.00"
}
```

`amountFiatUsd` is mutually exclusive with `amount`. Range $1.00 to $50,000.00. If BTC/USD spot is unavailable at request time, the route fails closed with HTTP 503 (`spot_unavailable`); a stale price would silently misalign the user-displayed Cash App figure.

<Warning>
  `amountFiatUsd` cannot be combined with `amountMode: "exact_out"`. The mode is always `exact_in`.
</Warning>

## API reference

### POST /v1/orchestration/onramp

Requires authentication (`Authorization: Bearer <api_key>`) and idempotency (`X-Idempotency-Key`).

**Request body:**

| Field              | Required | Type                                       | Notes                                                                                                                                                                                                                                                                                   |
| ------------------ | -------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `destinationChain` | Yes      | string                                     | Target chain (e.g., `spark`, `solana`, `base`)                                                                                                                                                                                                                                          |
| `destinationAsset` | Yes      | string                                     | Target asset (e.g., `USDB`, `BTC`, `USDC`, `PathUSD`, `ETH`, `WBTC`, `cbBTC`)                                                                                                                                                                                                           |
| `recipientAddress` | Yes      | string                                     | Destination address on the target chain                                                                                                                                                                                                                                                 |
| `amount`           | One of   | string                                     | Integer string. With `exact_in` (default): BTC amount in sats. With `exact_out`: destination asset amount in smallest units. Stablecoin exact-out amounts must be whole-cent values, for example multiples of `10000` for 6-decimal USDC/USDB. Mutually exclusive with `amountFiatUsd`. |
| `amountFiatUsd`    | One of   | string                                     | Sender's USD amount as a decimal string (e.g., `"100.00"`). Range $1.00 to $50,000.00. Forces `amountMode: "exact_in"`. Mutually exclusive with `amount`.                                                                                                                               |
| `amountMode`       | No       | string                                     | `"exact_in"` (default) or `"exact_out"`. When `exact_out`, the `amount` field specifies how much the recipient receives, and Orchestra calculates the required Lightning payment. Not supported on direct BTC passthrough routes. Ignored when `amountFiatUsd` is set.                  |
| `slippageBps`      | No       | number                                     | Slippage tolerance in basis points (default: 50)                                                                                                                                                                                                                                        |
| `appFees`          | No       | array                                      | Inline affiliate fees (mutually exclusive with `affiliateId`/`affiliateIds`)                                                                                                                                                                                                            |
| `affiliateId`      | No       | string                                     | Registered affiliate ID (mutually exclusive with `appFees`)                                                                                                                                                                                                                             |
| `affiliateIds`     | No       | `Array<string \| { affiliateId, feeBps }>` | Multiple registered affiliate IDs. Each entry is either a plain affiliate id or an object `{ affiliateId, feeBps }` that overrides the profile's fee bps for this order only. Mutually exclusive with `appFees`.                                                                        |

Exactly one of `amount` or `amountFiatUsd` must be set.

**Response fields:**

| Field                   | Type      | Notes                                                                                                                                  |
| ----------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `orderId`               | string    | Operation ID for tracking                                                                                                              |
| `quoteId`               | string    | Underlying quote ID                                                                                                                    |
| `depositAddress`        | string    | BOLT11 Lightning invoice                                                                                                               |
| `paymentLinks.cashApp`  | string    | Payment app deeplink URL                                                                                                               |
| `paymentLinks.shortUrl` | string    | Short redirect URL (302s to payment deeplink)                                                                                          |
| `amountIn`              | string    | BTC amount in sats                                                                                                                     |
| `estimatedOut`          | string    | Estimated output in destination asset smallest units                                                                                   |
| `feeAmount`             | string    | Platform fee                                                                                                                           |
| `feeBps`                | number    | Fee rate in basis points for this route                                                                                                |
| `totalFeeAmount`        | string    | Total fees (platform + app fees)                                                                                                       |
| `roundingFeeAmount`     | string    | Stablecoin surplus retained when delivery is capped to a whole-cent or exact-out target. Omitted or `"0"` when no surplus is retained. |
| `feeAsset`              | string    | Fee denomination (`USDB`, `BTC`, or the destination-side stable asset depending on route)                                              |
| `route`                 | string\[] | Asset path, not pipeline step names                                                                                                    |
| `expiresAt`             | string    | ISO timestamp when the Lightning invoice expires                                                                                       |

## Affiliate fees

The onramp endpoint supports the same affiliate fee model as the standard quote flow. Pass either `appFees` (inline), `affiliateId` (single registered affiliate), or `affiliateIds` (multiple registered affiliates, each a string or a `{ affiliateId, feeBps }` object to override the profile's bps for that order only) to collect fees on each onramp transaction. Affiliate fees require `amountMode: "exact_in"` (the default).

```json theme={null}
{
  "destinationChain": "spark",
  "destinationAsset": "USDB",
  "recipientAddress": "spark1...",
  "amount": "100000",
  "affiliateId": "my-partner"
}
```

Affiliate fees require a stablecoin settlement path and are not supported on `lightning:BTC -> spark:BTC` or `lightning:BTC -> bitcoin:BTC`. See [Affiliate Fees](/products/orchestration/api/resources) for registration and claim details.

## Webhook payloads

Register a webhook endpoint via `POST /v1/webhooks` to receive order status updates. See [Webhooks](/products/orchestration/webhooks) for endpoint setup, signature verification, and delivery semantics.

### Event sequence

The events fired depend on the destination route:

| Route                                                           | Events                                                                                                                      |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `lightning:BTC -> spark:USDB`                                   | `order.processing` -> `order.confirming` -> `order.swapping` -> `order.delivering` -> `order.completed`                     |
| `lightning:BTC -> spark:BTC` or `lightning:BTC -> bitcoin:BTC`  | `order.processing` -> `order.confirming` -> `order.delivering` -> `order.completed`                                         |
| `lightning:BTC -> solana:USDC` (and other bridged destinations) | `order.processing` -> `order.confirming` -> `order.swapping` -> `order.bridging` -> `order.delivering` -> `order.completed` |

If the order fails at any step, `order.failed` is emitted instead. If a refund is initiated, `order.refunding` and then `order.refunded` are emitted.

### Payload structure

Every webhook is an HTTP `POST` with `Content-Type: application/json`. The payload is an envelope containing the event name, a timestamp, and a snapshot of the order at the moment the event was emitted:

```json theme={null}
{
  "event": "order.<status>",
  "timestamp": "2026-01-15T12:05:00.000Z",
  "data": { ... }
}
```

### Example: `lightning:BTC -> spark:USDB` (completed)

The native USDB route skips bridging and delivery steps. The order completes at the swap step.

```json theme={null}
{
  "event": "order.completed",
  "timestamp": "2026-01-15T12:05:12.000Z",
  "data": {
    "id": "ord_abc123",
    "type": "order",
    "status": "completed",
    "quoteId": "q_xyz789",
    "amountIn": "100000",
    "amountOut": "96543210",
    "feeBps": 20,
    "feeAmount": "19308",
    "slippageBps": 50,
    "source": {
      "chain": "lightning",
      "asset": "BTC",
      "address": null,
      "txHash": null,
      "sweepTxHash": null
    },
    "destination": {
      "chain": "spark",
      "asset": "USDB",
      "address": "spark1RecipientAddress...",
      "txHash": null
    },
    "depositAddress": "lnbc1000n1pj...",
    "recipientAddress": "spark1RecipientAddress...",
    "flashnetRequestId": null,
    "sparkTxHash": "a1b2c3d4...",
    "refund": {
      "asset": null,
      "amount": null,
      "txHash": null
    },
    "error": null,
    "createdAt": "2026-01-15T12:04:00.000Z",
    "updatedAt": "2026-01-15T12:05:12.000Z",
    "completedAt": "2026-01-15T12:05:12.000Z",
    "swap": {
      "version": 1,
      "legs": {
        "full": {
          "provider": "flashnet",
          "poolId": "pool_btc_usdb",
          "assetInAddress": "BTC",
          "assetOutAddress": "USDB",
          "amountIn": "100000",
          "expectedAmountOut": "96543210",
          "minAmountOut": "96060493",
          "amountOut": "96543210",
          "slippageBpsRequested": 50,
          "slippageBpsUsed": 50,
          "simulation": {
            "executionPrice": "96543.21",
            "priceImpactPct": "0.01",
            "feePaidAssetIn": null,
            "warningMessage": null
          },
          "execution": {
            "executionPrice": "96543.21",
            "feeAmount": null,
            "requestId": "req_...",
            "outboundTransferId": null
          },
          "recordedAt": "2026-01-15T12:05:10.000Z"
        }
      }
    }
  }
}
```

### Example: `lightning:BTC -> solana:USDC` (completed)

Bridged routes add bridging and delivery steps. The `destination.txHash` contains the Solana transaction signature.

```json theme={null}
{
  "event": "order.completed",
  "timestamp": "2026-01-15T12:05:45.000Z",
  "data": {
    "id": "ord_def456",
    "type": "order",
    "status": "completed",
    "quoteId": "q_abc012",
    "amountIn": "100000",
    "amountOut": "96340000",
    "feeBps": 40,
    "feeAmount": "38536",
    "slippageBps": 50,
    "source": {
      "chain": "lightning",
      "asset": "BTC",
      "address": null,
      "txHash": null,
      "sweepTxHash": null
    },
    "destination": {
      "chain": "solana",
      "asset": "USDC",
      "address": "So1anaRecipientAddress...",
      "txHash": "5kWx7Y..."
    },
    "depositAddress": "lnbc1000n1pj...",
    "recipientAddress": "So1anaRecipientAddress...",
    "flashnetRequestId": null,
    "sparkTxHash": "e5f6g7h8...",
    "refund": {
      "asset": null,
      "amount": null,
      "txHash": null
    },
    "error": null,
    "createdAt": "2026-01-15T12:04:00.000Z",
    "updatedAt": "2026-01-15T12:05:45.000Z",
    "completedAt": "2026-01-15T12:05:45.000Z",
    "swap": {
      "version": 1,
      "legs": {
        "full": {
          "provider": "flashnet",
          "poolId": "pool_btc_usdb",
          "assetInAddress": "BTC",
          "assetOutAddress": "USDB",
          "amountIn": "100000",
          "expectedAmountOut": "96740000",
          "minAmountOut": "96256300",
          "amountOut": "96740000",
          "slippageBpsRequested": 50,
          "slippageBpsUsed": 50,
          "simulation": {
            "executionPrice": "96740.00",
            "priceImpactPct": "0.01",
            "feePaidAssetIn": null,
            "warningMessage": null
          },
          "execution": {
            "executionPrice": "96740.00",
            "feeAmount": null,
            "requestId": "req_...",
            "outboundTransferId": null
          },
          "recordedAt": "2026-01-15T12:05:20.000Z"
        }
      }
    }
  }
}
```

### Example: `order.failed`

When an order fails, the `error` object contains a code and message. All other fields reflect the state at the time of failure.

```json theme={null}
{
  "event": "order.failed",
  "timestamp": "2026-01-15T12:06:30.000Z",
  "data": {
    "id": "ord_ghi789",
    "type": "order",
    "status": "failed",
    "quoteId": "q_fail01",
    "amountIn": "100000",
    "amountOut": null,
    "feeBps": 20,
    "feeAmount": "19308",
    "slippageBps": 50,
    "source": {
      "chain": "lightning",
      "asset": "BTC",
      "address": null,
      "txHash": null,
      "sweepTxHash": null
    },
    "destination": {
      "chain": "spark",
      "asset": "USDB",
      "address": "spark1RecipientAddress...",
      "txHash": null
    },
    "depositAddress": "lnbc1000n1pj...",
    "recipientAddress": "spark1RecipientAddress...",
    "flashnetRequestId": null,
    "sparkTxHash": null,
    "refund": {
      "asset": null,
      "amount": null,
      "txHash": null
    },
    "error": {
      "code": "slippage_exceeded",
      "message": "Pool moved past slippage tolerance between deposit and execution"
    },
    "createdAt": "2026-01-15T12:04:00.000Z",
    "updatedAt": "2026-01-15T12:06:30.000Z",
    "completedAt": "2026-01-15T12:06:30.000Z"
  }
}
```

### Optional fields

These fields appear in `data` only when applicable:

| Field                                                  | When present                                                                                  |
| ------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| `swap`                                                 | After the swap step completes                                                                 |
| `paymentIntent`                                        | When `amountMode: "exact_out"` was used or `amountFiatUsd` was set                            |
| `amountFiatUsd`, `amountFiatCurrency`, `spotUsdPerBtc` | When the order was created with `amountFiatUsd`. Mirrored to the top level for direct access. |
| `feePlan`                                              | When `appFees`, `affiliateId`, or `affiliateIds` was passed at order creation                 |
| `feePayouts`                                           | Once fee settlement transactions are recorded                                                 |

`zeroconfOffer` does not appear on onramp orders. It applies only to Bitcoin L1 deposits.

### Unit testing webhooks

To verify your webhook handler, use the signature verification logic from [Webhooks](/products/orchestration/webhooks#signature-verification). Here's a self-contained test using the payloads above:

```ts theme={null}
import crypto from 'node:crypto';

// Your webhook secret (from POST /v1/webhooks response)
const secret = 'whsec_test_secret';

// Simulate a webhook delivery
const payload = JSON.stringify({
  event: 'order.completed',
  timestamp: '2026-01-15T12:05:12.000Z',
  data: {
    id: 'ord_abc123',
    type: 'order',
    status: 'completed',
    // ... full payload from examples above
  },
});

const timestamp = Date.now().toString();
const signature = crypto
  .createHmac('sha256', secret)
  .update(`${timestamp}.${payload}`)
  .digest('hex');

// In your test, send an HTTP POST to your handler with:
// - Body: payload
// - Header X-Flashnet-Signature: signature
// - Header X-Flashnet-Timestamp: timestamp
// - Header Content-Type: application/json

// Your handler should:
// 1. Verify the signature before parsing
// 2. Process the event idempotently (safe key: data.id + event + timestamp)
// 3. Return 2xx only after persisting the event
```

For the Flashnet TypeScript SDK, use `@flashnet/sdk`'s `verifyWebhookSignature` helper instead of rolling your own HMAC.

## Next steps

* [USDB Rewards](/usdb/rewards) for yield details
* [API Overview](/products/orchestration/api/overview) for authentication and error handling
* [Order Lifecycle](/products/orchestration/order-lifecycle) for status transitions and webhook payloads
* [Webhooks](/products/orchestration/webhooks) for real-time order updates
