Use cases

EUR → USDC payout

A European business wants to pay a counterparty in stablecoin. You collect euros locally over SEPA, and the beneficiary receives USDC on-chain — sent to a wallet address on the network you pick. Settlement between the two sides is handled internally — you never touch it. This recipe walks the whole corridor end to end, and shows where crypto delivery differs from a fiat payout.

Collect EUR over SEPA Pay out USDC on-chain Settlement internal · automatic

Prerequisites

You'll need workspace credentials and a short-lived bearer token. Every request below assumes $VB_TOKEN holds a valid access token — see the Authentication guide for how to obtain and refresh one.

Run the whole flow against https://api-stage.virtuabroker.com first. Stage behaves like production but moves no real money and settles to a test wallet, so you can drive the corridor as many times as you need before going live.

Step-by-step walkthrough

Six steps take you from an empty workspace to a settled USDC payout: discover what the corridor accepts, lock a rate, place the order against a wallet, fund the euro side, react to the webhook, and reconcile. A crypto payout skips the fiat destination-schema step — instead you choose a network and a wallet address.

Discover what the corridor accepts

Confirm the two ends are available before you quote. GET /v1/currencies lists the currencies your workspace can collect (origin) and pay out (destination); for a crypto destination it also carries the networks the asset settles on.

GET /v1/currencies
curl https://api.virtuabroker.com/v1/currencies \
  -H "Authorization: Bearer $VB_TOKEN"
200 · response
{
  "ok": true,
  "value": {
    "availableOriginCurrencies": [
      { "symbol": "EUR", "name": "Euro", "type": "fiat" }
    ],
    "availableDestinationCurrencies": [
      {
        "symbol": "USDC",
        "name": "USD Coin",
        "type": "crypto",
        "networks": [
          { "id": "MATIC", "name": "Polygon" },
          { "id": "ETH",   "name": "Ethereum" },
          { "id": "SOL",   "name": "Solana" },
          { "id": "TRON",  "name": "Tron" }
        ]
      }
    ],
    "holdableCurrencies": [ "EUR", "USDC" ]
  }
}

Then confirm the trading pair exists. GET /v1/pairs returns the pairs you can quote, each as a BASE/QUOTE string — USDC against EUR renders as USDC/EUR.

GET /v1/pairs
curl https://api.virtuabroker.com/v1/pairs \
  -H "Authorization: Bearer $VB_TOKEN"
200 · response
{
  "pairs": [ "USDT/EUR", "USDC/EUR" ]
}

A crypto payout does not use GET /v1/currencies/{currency}/destination-schema — that endpoint describes field-based fiat corridors. For USDC there are no bank fields to fill in: you pick a network (one of the networks above) and provide a wallet address. You'll commit both at order time in step 3.

Lock a quote

A quote fixes the rate and fees for the pair for a short window. Quote from the euro side you're collecting: pass originAmount in EUR and let the API return how much USDC the beneficiary receives. Pin the payout network with destinationRail — for crypto the rail is the chain — so the quote's gas estimate reflects the network you'll actually pay on. USDC settles on ETH, MATIC (Polygon — the low-fee default), SOL, and TRON; omit destinationRail to accept the workspace default network.

POST /v1/quotes
curl https://api.virtuabroker.com/v1/quotes \
  -H "Authorization: Bearer $VB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "originCurrency": "EUR",
    "destinationCurrency": "USDC",
    "destinationRail": "MATIC",
    "originAmount": 1000
  }'
200 · response
{
  "quoteId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
  "originCurrency": "EUR",
  "originAmount": 1000,
  "destinationCurrency": "USDC",
  "destinationNetwork": "MATIC",
  "destinationAmount": 1081.53,
  "pair": "USDC/EUR",
  "price": 0.9223,
  "conversionRate": 1.0842,
  "fee": 2.5,
  "feeCurrency": "EUR",
  "expectedGasFee": 0.25,
  "expireAt": 1694291200
}

Hold onto quoteId (a UUID) — the beneficiary will receive destinationAmount in USDC. The rate is quoted on the pair (USDC/EUR); price is USDC priced in EUR while conversionRate reads as 1 EUR → X USDC. expectedGasFee is the estimated on-chain cost for the network you pinned. Amounts are JSON numbers, never strings. Create the order before expireAt (a unix timestamp in seconds), or lock a fresh quote.

Create the order against a wallet

Commit the quote to a real destination. For a crypto payout, destinationTransferData is the network literal as type plus the beneficiary address (add an optional memo for chains that need one). Because the euro side is funded over SEPA (not a workspace balance), you must also include userData — the payer — so the deposit can be provisioned; if the payer email is new, send their address and name fields too. Tag the order with your own externalReference; order reads return it as externalRefeference (note the spelling). Writes are not de-duplicated, so if this call times out, reconcile on that field before resending rather than risking a doubled payout (see Retrying a write).

POST /v1/orders
curl https://api.virtuabroker.com/v1/orders \
  -H "Authorization: Bearer $VB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "quoteId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
    "userData": {
      "userEmail": "payer@example.com",
      "firstName": "Lucía",
      "lastName": "García",
      "address": "Calle Gran Vía 12",
      "city": "Madrid",
      "postalCode": "28013",
      "state": "Madrid",
      "country": "ES",
      "taxId": "12345678Z"
    },
    "destinationTransferData": { "type": "MATIC", "address": "0x1A2b3C4d5E6f7890AbCdEf1234567890AbCdEf12" },
    "externalReference": "payout-inv-8842"
  }'
200 · response
{
  "orderId": "A7K9DP2X4Q1M",
  "status": "Pending",
  "createdAt": 1694290300,
  "fee": 0,
  "feeCurrency": "EUR",
  "deposit": {
    "status": "Pending",
    "depositInfo": {
      "depositType": "SEPA",
      "iban": "ES00 0000 0000 0000 0000 0000",
      "referenceCode": "VB-8F3K2P",
      "amount": 1000
    }
  }
}

The order opens in Pending — the on-chain payout is armed but held until the euro side is funded. deposit.depositInfo tells your customer exactly where to send the EUR: the iban to pay and the referenceCode to quote.

At creation the order's fee is 0 — the final fees aren't known yet. They populate as the order executes; read the authoritative fee from a GET /v1/orders/{orderId} when you reconcile.

On-chain transfers are irreversible. The type must match a network the wallet actually holds USDC on, and the address must be correct for that chain — a wrong network or a mistyped address cannot be recovered. Validate both before you submit.

Fund the collection

The EUR side is collected through a deposit. Give the paying business the deposit.depositInfo from the order response: they transfer the euros to that IBAN over SEPA, quoting the supplied referenceCode. When the funds land, the deposit moves from Pending to Success, the workspace balance is credited automatically, and the order moves from Pending to Processing — no extra API call is needed from you.

SEPA transfers can take from minutes to a business day depending on the sending bank. Treat funding as asynchronous and let the webhook (next step) drive your side. For the full deposit object and its states, see deposits in the API reference.

React to webhooks

Rather than polling, subscribe to order_status_updated — it fires on every status change, so check data.status and act when it reaches Success (the USDC has been sent on-chain) or Failed. Endpoints you register through POST /v1/webhooks receive a signed X-VirtuaBroker-Signature-256 header — verify it before you trust the body. See the Webhooks guide for the verification recipe.

POST /your-webhook-endpoint · order_status_updated
{
  "id": "A7K9DP2X4Q1M",
  "type": "order_status_updated",
  "api_version": "1.0",
  "data": {
    "orderId": "A7K9DP2X4Q1M",
    "status": "Success"
  }
}

The envelope id is the order's id and stays the same on every update for that order — it is not a per-delivery event id, so don't dedupe on it. There is no automatic retry; if you miss a delivery, pull it back with POST /v1/webhooks/resend or reconcile data.status against a GET (next step), which is the source of truth.

Reconcile

Match the event back to your ledger on orderId, which you stored against the externalReference you set at order creation, then confirm the final state authoritatively with a direct read. The GET is the source of truth if a webhook is ever delayed or missed.

GET /v1/orders/{orderId}
curl https://api.virtuabroker.com/v1/orders/A7K9DP2X4Q1M \
  -H "Authorization: Bearer $VB_TOKEN"

Full status flow

The API reports a EUR → USDC order with the same small set of statuses it uses for every order. It starts in Pending while the euro collection is awaited; once the deposit credits, it moves to Processing while the exchange and the on-chain USDC transfer run; then it ends in Success. If a step can't be finished it ends in Failed; a cancelled order ends in Canceled. (An order held for compliance shows Reviewing.) The internal exchange/withdrawal steps are collapsed into Processing — see the Statuses reference.

Pending Processing Success Failed
← Prev
EUR → BRL payout