Skip to main content
This page is a self-contained sandbox walkthrough of the Virtual Account flow. Work through it top to bottom and you will finish with a settled onramp transaction. Every command below is copy-pasteable as written — the only thing you supply is your sandbox API key. For the product overview and production wiring, see the Virtual Account Integration Guide.

What you can and cannot test in sandbox

Provider sandboxes do not move real money, so not every path can be completed. Read this before you start, so you know where each flow is expected to stop.
Not covered by this page. These are separate product surfaces with their own sandbox guides, and none can be exercised from the Virtual Account flow: fiat card and ACH payments (Payments Stack), Bank Linking, and headless Apple Pay (Mercuryo).
The steps below use Noah, which reaches a settled transaction in the fewest moves. Due Network and Brale follow the same steps with the values in Provider test values.Each provider is enabled per account and carries its own KYC approval, so confirm both in Step 2 before you start. A provider whose KYC is not APPROVED returns 403 KYC_NOT_COMPLETED.

Prerequisites

1

A sandbox API key

From the Meld dashboard, Developer → API keys, in the sandbox environment. Sandbox and production keys are not interchangeable.
2

curl and jq

jq is only used to pull one id out of a response. On macOS it ships with the OS; on Debian or Ubuntu, apt install jq.
3

Whitelabel mode enabled on your account

Ask Meld support to enable whitelabel mode (headlessOnrampsEnabled). It is account-wide rather than per-provider, and without it every virtual account call fails with 403 WHITELABEL_NOT_ENABLED, even for a fully KYC-approved customer.
4

At least one virtual-account provider enabled

Noah is the one this guide uses. Ask Meld support to confirm it is enabled on your sandbox account.

Step 1 — Set up your shell

Paste your sandbox key into the first line. Everything after this is copy-paste.
Your sandbox account includes a pre-created customer that is already through SumSub KYC and onboarded with the virtual account providers. Resolve it to a customerId and keep it in your shell:
Expected: a base58 id such as WmYNx6LnmqasLfcp8HngoD. If you get null, the pre-created customer is not on your account — create your own using Using your own customer instead.
The pre-created customer lets you skip customer creation, SumSub KYC, and the provider-hosted questionnaire — the three steps that cannot be automated. That is the fastest route to a settled transaction. To exercise the full onboarding path yourself, see Using your own customer.

Step 2 — Confirm the customer is ready

Expected: 200, with the provider you intend to use reading "kycStatus": "APPROVED":
You need the provider’s status at APPROVED, not just SumSub’s — SumSub approval alone is not enough to transact. A provider listed as PENDING has been shared but has not finished its own hosted questionnaire, and every call to it returns 403 KYC_NOT_COMPLETED until it does. Complete that provider’s questionnaire as described in Using your own customer. A provider missing from the list entirely is either not enabled on your account or has not been KYC-shared to this customer.

Step 3 — Get a quote

This is the shared headless quote endpoint, not a virtual-account-only one. Always include paymentMethodType, or Meld falls back to a card quote instead of the bank quotes the virtual account flow needs.
Expected: 200 with at least one quote. serviceProvider is the value you carry into the next step:
An empty quotes array with NO_VALID_QUOTES means no provider serves that corridor and rail for your account — check your values against Provider test values.
This endpoint also accepts externalCustomerId in place of customerId, which is convenient if you have not resolved the Meld id yet. The provisioning and simulation endpoints in Steps 4 and 5 do not — they take customerId only.

Step 4 — Provision the virtual account

This returns the standing bank details your customer would send fiat to. Fetch it once per customer, provider, payment method, currency pair, and destination wallet — it is reusable, not per-transaction.
Expected: 201 with bank or IBAN coordinates.
A 202 with a Retry-After header means the provider is still provisioning asynchronously. Poll with the same X-Idempotency-Key until you get the 201.
This endpoint takes the Meld customerId only. Unlike the quote endpoint, it does not accept externalCustomerId — sending one without customerId returns 400 [customerId] must not be blank.

Step 5 — Simulate a deposit

You cannot send real fiat to a sandbox virtual account, so nothing would trigger settlement. This endpoint creates a test deposit into the standing account from Step 4, and the provider’s normal webhooks settle it exactly as in production.
Sandbox only — in production this endpoint returns 404. Onramp only; there is no offramp equivalent.
Repeat the provider, rail, currencies, and destination wallet from Step 4, and set amount to the amount you want deposited.
Expected: 200 with a provider reference:
For Due Network, swap in "serviceProvider": "DUENETWORK", "paymentMethodType": "SEPA", "sourceCurrencyCode": "EUR", and a token such as USDT_TRON. Leave network out entirely for both providers; it is a Uphold-only field.

Step 6 — Confirm settlement

Settlement is asynchronous, so the transaction does not exist yet when Step 5 returns. In a real integration you react to the TRANSACTION_CRYPTO_* webhooks from Payins. For a scripted test, poll instead:
Expected: 200. The newest transaction starts at PENDING and reaches SETTLED — typically 30 to 90 seconds for Noah, sometimes longer for Due.
Re-run the command until status reads SETTLED. That is the end of the onramp flow. A transaction carries many more fields than shown here — see Payins → Fetch the full transaction for the full shape.

Using your own customer

Skip this if you used the pre-created customer. Use it when you want to exercise onboarding yourself.
The hosted questionnaire cannot be automated. Meld shares the SumSub verification result with each provider, along with the standard agreement acceptances for Noah, but it does not answer the provider’s questionnaire on your customer’s behalf. Someone has to open the provider-hosted page and complete it — there is no API that bypasses this step.
Create a customer.
Expected: 200 and a base58 id. For a business customer for KYB testing, contact Meld. Share SumSub KYC to the providers. One SumSub applicant approval is reused across providers — the customer verifies identity once and Meld shares the result to each provider you name. For the full walkthrough see KYC testing → Meld KYCes the user.
Expected: 200 with a hosted SumSub url. Send the customer there to complete identity verification. A 409 means that provider was already shared. Re-issue with PATCH instead — it takes the same body as the POST, so serviceProvider and mode are required alongside kycShareProviders, and omitting them returns 400:
mode can also be TOKEN_IMPORT when the customer already has a SumSub applicant or token, or RAW_DATA_IMPORT via the multipart variant. Complete the provider-hosted questionnaire. After SumSub approval, a provider may still require its own step and sit at PENDING. The link lives on the customer record, at that provider’s entry in serviceProviderCustomers[]:
The questionnaire is outstanding if kyc.additionalInfo.HostedURL is present and kyc.status is not APPROVED, REJECTED, or EXPIRED. There is no separate “questionnaire complete” flag, and no intermediate “submitted, awaiting review” state — status moves to APPROVED or REJECTED asynchronously once the provider’s webhook fires. If a hosted link has gone stale — Due’s hosted-session tokens expire — re-issue the share with the PATCH above, then re-fetch the customer for the fresh HostedURL.
Due Network terms of service. Due maintains its own terms acceptance, separate from KYC, and a customer can be KYC-approved with terms still outstanding. Re-issuing the share with the PATCH above refreshes it: Meld re-reads Due’s account and, while the terms are pending, writes the current link to serviceProviderCustomers[].kyc.additionalInfo.TosURL on the customer record.The link is not in the PATCH response — that returns the SumSub URL. Re-fetch GET /accounts/customers/{customerId} and read TosURL from there.If TosURL is absent after a re-issue, Due is reporting the terms as already accepted. If Due still rejects the order for terms, contact Meld.
Due runs a second approval. Due’s own KYC/KYB review is independent of the questionnaire, so a customer can complete the hosted page and still be rejected at order time with err_kyc_not_passed while Due’s account import is still in progress. If it does not clear, contact Meld.

Provider test values

Noah

Use the same values for onramp and offramp.

Due Network

Due does not operate in the US. Use a non-US customer and EUR as the fiat currency.
Onramp (buy) Offramp (sell)

Brale

Brale does not support quotes. All exchanges are 1:1 between USD and USD-backed stablecoins. Brale also has no sandbox deposit simulator, so the flow stops at Step 4.
Onramp (buy)

Troubleshooting

Testing tips

  • Wallet addresses: any valid address for the token’s chain. Some providers validate the format matches — a TRON address for USDT_TRON, for example.
  • Start with the onramp (buy) flow. It is the only path that reaches SETTLED in sandbox.
  • Discover currencies and rails: call GET /network-partner/supported/currencies with type=CRYPTO, the partner name, and category=CRYPTO_VIRTUAL_ACCOUNT_ONRAMP (or CRYPTO_VIRTUAL_ACCOUNT_OFFRAMP).
  • Idempotency: include X-Idempotency-Key on provisioning requests so you can safely retry without creating duplicates.
  • Partner discovery is not account enablement. GET /network-partner/supported/partners lists what the catalog supports, which can be more than what is enabled on your account.