Skip to main content
Many popular apps support zero-fee Lightning payments. Orchestra combines this 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. USDB is a dollar-pegged stablecoin backed 1:1 by US Treasuries, and it earns 3.5-6% APY paid daily in BTC. 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 on any supported chain) work through the same pipeline but add swap, bridge, and delivery steps.
The fiat onramp is not available to residents of New York City.

How It Works

  1. Your app calls POST /v1/orchestration/onramp with the destination chain, asset, recipient address, and amount. The amount can be BTC sats to send (exact_in, the default) or the destination asset amount to receive (exact_out).
  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 entire flow is a single API call. No separate quote and submit steps.

Why 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 SparkBTC on Spark / Bitcoin L1USDC on Solana/Base
Platform feeCustom pricingCustom pricingCustom pricing
Fee assetUSDBBTCUSDC
Pipeline shapeVerify, swapVerify, settle BTC fee, deliverVerify, swap, bridge, deliver
Delivery timeSecondsSecondsSeconds
Sweep feeNoneNoneMay apply (details)
Rewards3.5-6% rewards in BTCNoneNone
USDB holders earn rewards automatically. A wallet holding 10+ USDB earns 3.5-6% rewards in BTC daily. Your users onramp to dollars that earn sats. See USDB Rewards for details.

Fees

Many Lightning-compatible apps charge zero fees on Lightning payments. Orchestra uses custom pricing based on route and volume, significantly lower than traditional onramps (which typically charge 1.5-3.5% for card-based or 0.5-1.5% for bank transfers). No intermediary fees and no card processing fees. The /estimate endpoint returns the exact fee for each route.
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.

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.
Validate amounts client-side against your payment app’s limits before calling the API.

Supported Routes

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
  • Many more routes…

Integration

1

Get a price estimate

Use the estimate endpoint to show the user what they’ll receive before committing.
curl 'https://orchestration.flashnet.xyz/v1/orchestration/estimate?\
sourceChain=lightning&sourceAsset=BTC&\
destinationChain=spark&destinationAsset=USDB&\
amount=100000'
Response:
{
  "estimatedOut": "96543210",
  "feeAmount": "19308",
  "feeBps": 20,
  "feeAsset": "USDB"
}
2

Create the onramp order

A single call creates the Lightning invoice, builds the order, and returns payment deeplinks.
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:
{
  "orderId": "op_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": "2025-01-15T12:04:00.000Z"
}
3

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.
// 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.
4

Track the order

Use SSE or polling to track progress. USDB orders skip the bridge and delivery steps, so they complete faster.
// 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());

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.”
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 minus any rounding.
Affiliate fees (appFees, affiliateId, affiliateIds) are not supported with exact_out. Use exact_in if you need affiliate fees on the transaction.
Exact-out is not supported for the direct BTC passthrough routes lightning:BTC -> spark:BTC and lightning:BTC -> bitcoin:BTC.

API Reference

POST /v1/orchestration/onramp

Requires authentication (Authorization: Bearer <api_key>) and idempotency (X-Idempotency-Key). Request body:
FieldRequiredTypeNotes
destinationChainYesstringTarget chain (e.g., spark, solana, base)
destinationAssetYesstringTarget asset (e.g., USDB, BTC, USDC, PathUSD, ETH)
recipientAddressYesstringDestination address on the target chain
amountYesstringAmount as an integer string. With exact_in (default): BTC amount in sats. With exact_out: destination asset amount in smallest units (e.g., 6 decimals for USDC/USDB).
amountModeNostring"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.
slippageBpsNonumberSlippage tolerance in basis points (default: 50)
appFeesNoarrayInline affiliate fees (mutually exclusive with affiliateId/affiliateIds)
affiliateIdNostringRegistered affiliate ID (mutually exclusive with appFees)
affiliateIdsNostring[]Multiple registered affiliate IDs (mutually exclusive with appFees)
Response fields:
FieldTypeNotes
orderIdstringOperation ID for tracking
quoteIdstringUnderlying quote ID
depositAddressstringBOLT11 Lightning invoice
paymentLinks.cashAppstringPayment app deeplink URL
paymentLinks.shortUrlstringShort redirect URL (302s to payment deeplink)
amountInstringBTC amount in sats
estimatedOutstringEstimated output in destination asset smallest units
feeAmountstringPlatform fee
feeBpsnumberFee rate in basis points for this route
totalFeeAmountstringTotal fees (platform + app fees)
feeAssetstringFee denomination (USDB, BTC, or the destination-side stable asset depending on route)
routestring[]Asset path, not pipeline step names
expiresAtstringISO 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) to collect fees on each onramp transaction. Affiliate fees require amountMode: "exact_in" (the default).
{
  "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 for registration and claim details.

Webhook Payloads

Register a webhook endpoint via POST /v1/webhooks to receive order status updates. See Webhooks for endpoint setup, signature verification, and delivery semantics.

Event Sequence

The events fired depend on the destination route:
RouteEvents
lightning:BTC -> spark:USDBorder.processing -> order.confirming -> order.swapping -> order.delivering -> order.completed
lightning:BTC -> spark:BTC or lightning:BTC -> bitcoin:BTCorder.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:
{
  "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.
{
  "event": "order.completed",
  "timestamp": "2026-01-15T12:05:12.000Z",
  "data": {
    "id": "op_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",
    "priceLock": {
      "version": 1,
      "mode": "approval_required",
      "amountIn": "100000",
      "quotedAmountOut": "96543210",
      "lockedMinAmountOut": "96060493",
      "slippageBps": 50,
      "createdAt": "2026-01-15T12:04:00.000Z",
      "expiresAt": "2026-01-15T12:06:00.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.
{
  "event": "order.completed",
  "timestamp": "2026-01-15T12:05:45.000Z",
  "data": {
    "id": "op_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",
    "priceLock": {
      "version": 1,
      "mode": "approval_required",
      "amountIn": "100000",
      "quotedAmountOut": "96340000",
      "lockedMinAmountOut": "95858300",
      "slippageBps": 50,
      "createdAt": "2026-01-15T12:04:00.000Z",
      "expiresAt": "2026-01-15T12:06:00.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.
{
  "event": "order.failed",
  "timestamp": "2026-01-15T12:06:30.000Z",
  "data": {
    "id": "op_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": "swap_slippage_exceeded",
      "message": "Swap output below minimum amount"
    },
    "createdAt": "2026-01-15T12:04:00.000Z",
    "updatedAt": "2026-01-15T12:06:30.000Z",
    "completedAt": "2026-01-15T12:06:30.000Z",
    "priceLock": {
      "version": 1,
      "mode": "approval_required",
      "amountIn": "100000",
      "quotedAmountOut": "96543210",
      "lockedMinAmountOut": "96060493",
      "slippageBps": 50,
      "createdAt": "2026-01-15T12:04:00.000Z",
      "expiresAt": "2026-01-15T12:06:00.000Z"
    }
  }
}

Optional Fields

These fields appear in data only when applicable:
FieldWhen present
priceLockAlways present for onramp orders (lightning source uses mode: "approval_required")
swapAfter the swap step completes
paymentIntentWhen amountMode: "exact_out" was used
feePlanWhen appFees, affiliateId, or affiliateIds was passed at order creation
feePayoutsOnce 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. Here’s a self-contained test using the payloads above:
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: 'op_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