Quickstart
This is the 30 minute quickstart guide for executing the virtual account flow for the first time.
The virtual account flow is available white label, aka fully API driven, from Meld. The user can buy or sell digital assets in your UI, besides going to their banking app to send money. You will build your own UI on top of Meld's APIs for this product.
In the Virtual Account flow, the onramp will create a virtual bank account for each user and return those bank details via Meld's API. In a buy transaction, the user will send money to that virtual bank account and receive crypto to their wallet. In a sell transaction the user will send crypto to the onramp and then be paid in fiat from the virtual bank account to the user's own bank account.
Integration Quickstart
Go through this flow to understand the key endpoints and make a successful transaction.
Time Required: 30 minutes
Difficulty: API knowledge required
Step 1: Get Your API Key
Your API key is required for all Meld API calls.
Process:
- Check your email for the Meld dashboard invitation
- Click the invitation link and log in
- In the dashboard, navigate to Developer > API Keys
- Click "Reveal Key"
- Copy and save your API key securely
Important: Always addBASICbefore your API key in all requestsExample:
BASIC W9kZTT7332okCEc1A9aqAq:3sYKoXQv6oHVHSts7G2agw9vTCXz
Step 2: Set Up Webhooks
Webhooks notify your server when transactions are created and updated. This step can be completed later.
Requirements:
- A publicly accessible URL (localhost will not work)
- For testing, consider using ngrok, webhook.site or similar services
Setup Process:
- Navigate to Developer > Webhooks in the dashboard
- Click "Add Endpoint"
- Enter your webhook URL (must start with http/https)
- Give your webhook a descriptive name
- Select "Subscribe to all events"
- Click "Add endpoint"
Note: You will receive notifications to this URL for all transaction creations and updates.
Step 3: Test Your API Connection
Verify your API key by requesting a price quote.
API Call Details:
- Endpoint: Get Crypto Quote
- Method:
GET - Authorization:
BASIC [your-api-key]
Request Body:
{
"countryCode": "US",
"sourceCurrencyCode": "USD",
"destinationCurrencyCode": "BTC",
"paymentMethodType": "CREDIT_DEBIT_CARD",
"sourceAmount": 100
}Testing Options:
- Use the interactive docs at the endpoint URL
- Use Meld's Postman collection: https://www.postman.com/meldeng/workspace/meld-io-public-api-collection
Expected Response:
✅ 200 status code with quote details
Here is an example of a single quote. You can expect multiple quotes, 1 per onramp, in the response.
{
"quotes": [
{
"transactionType": "CRYPTO_PURCHASE",
"sourceAmount": 100.00,
"sourceAmountWithoutFees": 95.82,
"fiatAmountWithoutFees": 95.82,
"destinationAmountWithoutFees": null,
"sourceCurrencyCode": "USD",
"countryCode": "US",
"totalFee": 4.18,
"networkFee": 0.28,
"transactionFee": 2.9,
"destinationAmount": 95.82,
"destinationCurrencyCode": "USDC",
"exchangeRate": 0.9582,
"paymentMethodType": "CREDIT_DEBIT_CARD",
"customerScore": 21.66,
"serviceProvider": "NOAH",
"institutionName": null,
"lowKyc": false,
"partnerFee": 1
}
]
}Step 4: Create a SubAccount
In order to complete this step, you must've already KYBed / KYCed with the onramp in question and received your serviceProviderAccountId from the onramp. This is a one time step per user / business to set up a subaccount with Meld. Brale calls this id an accountId while Noah cals it a customerId.
API Call Details:
- Endpoint: Create SubAccount
- Method:
POST - Authorization:
BASIC [your-api-key]
Request Body:
{
"serviceProvider": "NOAH",
"name": "My First Customer",
"serviceProviderAccountId": "2nLpyIBLJDtlsesN8bgQGZYZbT5"
}Expected Response:
✅ 200 status code with confirmation that the subAccount was created
{
"id": "WeP9eoFziQX4yXE5iiGqJE",
"name": "My First Customer",
"status": "ACTIVE"
},
"serviceProviderSubAccount": {
"id": "2nLpyIBLJDtlsesN8bgQGZYZbT5",
"serviceProvider": "NOAH",
"serviceProviderAccessProfileId": "W9233jn2jn2jncjd",
"status": "ACTIVE"
}
}You will need the id from this response to use for all orders you make.
Step 5: Create an Onramp Order
Note that in sandbox, you will not be able to complete a transaction, even a test one, so this is the last step you will get to. This endpoint needs to be called once per transaction.
API Call Details:
- Endpoint: Create Buy Order
- Method:
POST - Authorization:
BASIC [your-api-key]
Request Body:
{
"subAccountId": "WeP9eoFziQX4yXE5iiGqJE",
"sourceAmount": 100.00,
"sourceCurrencyCode": "USD",
"destinationCurrencyCode": "USDC",
"destinationWalletAddress": "0xC38dA09A61d2686aB64BF119Da37110Ecd089efE",
"paymentMethodType": "LOCAL_BANK_TRANSFER",
"serviceProvider": "NOAH"
}
Note: Replace thewalletAddresswith your own
Expected Response:
✅ 200 status code with the virtual bank account information to send money to.
{
"orderId": "WeP9eoFziQX4yXE5abcfec",
"paymentMethodType": "ACH",
"receivingBankInformation": {
"accountNumber": "12345678",
"routingNumber": "987654321"
},
"serviceProviderDetails": {
<raw data from the onramp>
}
}This is as far as you can get in sandbox. You cannot send money to the virtual account to complete the transaction. However in production you will be able to do so, and this is how the flow continues.
Save the orderId to use to fetch the transaction details.
Step 6: Complete the Transaction:
Go to your bank app and initiate a transfer of the amount to the bank information of the virtual bank account, and hit send. Note that the transfer time depends on your bank and the bank transfer rails in your country.
Step 7: Verify Your Transaction
Fetch Transaction Details after Receiving Webhooks
- Check your webhook endpoint for a webhook that the transaction has been created.
Expected Webhook Response
{
"eventType": "TRANSACTION_CRYPTO_PENDING",
"eventId": "AAsuLXHXD3mS1cjNBuHHzv",
"timestamp": "2022-02-24T16:36:41.717262Z",
"accountId": "WQ5RyhdFzE45qjsomdzQ1u",
"profileId": "W9ka8vLE4ufBkSg3BEciZb",
"version": "2025-03-01",
"payload": {
"virtualAccountRampOrderId": "WeP9eoFziQX4yXE5abcfec",
"requestId": "f07f1accb7404aec9bd9a5d64975eed1",
"accountId": "W2aRZnYGPwhBWB94iFsZus",
"paymentTransactionId": "WePZCYJW7cdXR7SxUMp8mE",
"customerId": "WePZCYZjAK97cJWokfH3Jc",
"externalCustomerId": "testCustomer",
"externalSessionId": "testSession",
"paymentTransactionStatus": "PENDING",
"transactionType": "CRYPTO_PURCHASE",
"sessionId": "WePjVaT4iBHPpqW49F419x"
}
}In the webhook, the virtualAccountRampOrderId value will match the orderId in the response of the previous step, allowing you to tie an order to it's corresponding webhook(s) and transaction.
- Extract the
paymentTransactionIdfrom the webhook payload - Call GET /payments/transactions/{transactionId}
Note: Alternatively you could use the virtualAccountRampOrderId to find the transaction by filtering by orderId on the search transactions endpoint.
Expected Response:
✅ 200 status code with transaction details
{
"transaction": {
"id": "WePZCYJW7cdXR7SxUMp8mE",
"parentPaymentTransactionId": null,
"accountId": "WQ5RyhdFzE45qjsomdzQ1u",
"isPassthrough": false,
"passthroughReference": null,
"isImported": false,
"customer": {
"id": "WePZCYZjAK97cJWokfH3Cc",
"accountId": "WQ5RyhdFzE45qjsomdzQ1u",
"externalId": "YC100"
},
"transactionType": "CRYPTO_PURCHASE",
"status": "SETTLED",
"sourceAmount": 100.00,
"sourceCurrencyCode": "USD",
"destinationAmount": 95.82,
"destinationCurrencyCode": "USDC",
"paymentMethodType": "LOCAL_BANK_TRANSFER",
"serviceProvider": "NOAH",
"serviceTransactionId": "1f0d470c-f5c2-6fc2-a54d-b6cdd6b6f014",
"orderId": null,
"description": null,
"externalReferenceId": "testSession",
"serviceProviderDetails": {
**raw data from the onramp**
},
"multiFactorAuthorizationStatus": null,
"createdAt": "2025-12-08T20:03:07.173223Z",
"updatedAt": "2025-12-08T20:08:08.877106Z",
"countryCode": "US",
"sessionId": "WePZCbr96gAUxgu7Auvm4q",
"externalSessionId": "testSession",
"paymentDetails": null,
"externalCustomerId": "testCustomer",
"fiatAmountInUsd": 100.00,
"sessionClientTags": null,
"serviceProviderTransactionUrl": null,
"serviceProviderCreatedAt": "2025-12-08T20:02:22Z",
"cryptoDetails": {
"sourceWalletAddress": null,
"destinationWalletAddress": "0xd72cc3468979360e31bc83b84f0887deccfd81d5",
"sessionWalletAddress": "0xd72cc3468979360e31bc83b84f0887deccfd81d5",
"totalFee": 4.18,
"networkFee": 0.28,
"transactionFee": 2.9,
"partnerFee": 1,
"totalFeeInUsd": 4.18,
"networkFeeInUsd": 0.28,
"transactionFeeInUsd": 2.9,
"partnerFeeInUsd": 1,
"blockchainTransactionId": "0x553d295955a978ed3e9fc1717b5bcb903c69577e49c8ad255abece945ffa9ba0",
"institution": null,
"chainId": "1"
}
}
}Dashboard Verification:
- Navigate to the Transactions tab
- If your transaction isn't visible:
- Click the Status dropdown
- Select "Select All"
- Your transaction should appear
✅ White-Label API Integration Complete! You can now build custom crypto experiences.
➡️ Build Custom UI Guide - Complete implementation guide
Next Steps
If you're ready to begin your integration, check out the end to end integration guide for the Virtual Account flow.
Troubleshooting
Common Issues and Solutions:
🚫 401 Unauthorized
- Ensure
BASICis added before your API key - Check for extra spaces or incorrect formatting
🚫 Webhook Not Received
- Verify URL is publicly accessible
- Check firewall settings
- Ensure webhook endpoint returns 200 status
🚫 Transaction Not Visible
- Change status filter to "Select All" in dashboard
- Wait 30 seconds and refresh
- Check if using correct environment (sandbox vs production)
Updated 5 days ago