Skip to Content
Core ConceptsVirtual Accounts

Virtual Accounts

Virtual Accounts are multi-currency balance accounts that hold your funds within the Koywe system, enabling seamless payment operations without traditional bank accounts.

What are Virtual Accounts?

A Virtual Account is a balance account in a specific currency, similar to a bank account but existing within the Koywe platform.

Key Characteristics

  • One per currency per merchant: Each merchant has a virtual account for each currency they work with
  • Automatically created: Generated when you create a merchant
  • Real-time balances: Instant updates as transactions occur
  • No bank account needed: Hold funds without opening multiple bank accounts
  • Used for all operations: Source and destination for PAYINs, PAYOUTs, and transfers

How Virtual Accounts Work

Virtual accounts serve as:

  • Receiving accounts for customer payments (PAYIN)
  • Holding accounts for funds in multiple currencies
  • Source accounts for provider payments (PAYOUT)
  • Transfer endpoints for currency exchanges (BALANCE_TRANSFER)
  • Funding source for crypto purchases (ONRAMP)
  • Destination for crypto sales (OFFRAMP)

Balance Types

Each virtual account tracks three types of balances:

1. Available Balance

The amount immediately available for use.

{ "availableBalance": 1000000, // 1,000,000 COP available "currencySymbol": "COP" }

Can be used for:

  • Creating PAYOUT orders
  • BALANCE_TRANSFER to other currencies
  • ONRAMP crypto purchases

2. Pending Balance

Funds that are being processed but not yet available.

{ "pendingBalance": 50000, // 50,000 COP pending "currencySymbol": "COP" }

Examples:

  • Customer payment being confirmed (PAYIN in PROCESSING status)
  • Crypto sale being settled (OFFRAMP in PROCESSING)

3. Reserved Balance

Funds temporarily locked for ongoing operations.

{ "reservedBalance": 25000, // 25,000 COP reserved "currencySymbol": "COP" }

Examples:

  • PAYOUT order in progress
  • BALANCE_TRANSFER being executed

Checking Balances

Get All Balances for a Merchant

async function getMerchantBalances(token, orgId, merchantId) { const response = await axios.get( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts/balances`, { headers: { 'Authorization': `Bearer ${token}` } } ); return response.data; } // Usage const balances = await getMerchantBalances(token, orgId, merchantId); balances.forEach(balance => { console.log(`${balance.currencySymbol}: ${balance.availableBalance} available`); });
def get_merchant_balances(token, org_id, merchant_id): response = requests.get( f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/accounts/balances', headers={'Authorization': f'Bearer {token}'} ) return response.json() # Usage balances = get_merchant_balances(token, org_id, merchant_id) for balance in balances: print(f"{balance['currencySymbol']}: {balance['availableBalance']} available")
curl -X GET 'https://api-sandbox.koywe.com/api/v1/organizations/YOUR_ORG_ID/merchants/YOUR_MERCHANT_ID/accounts/balances' \ -H 'Authorization: Bearer YOUR_TOKEN'

Response:

[ { "id": "va_cop_12345", "currencySymbol": "COP", "availableBalance": 1000000, "pendingBalance": 50000, "reservedBalance": 25000, "totalBalance": 1075000, "merchantId": "mrc_xyz789" }, { "id": "va_usd_67890", "currencySymbol": "USD", "availableBalance": 500, "pendingBalance": 0, "reservedBalance": 0, "totalBalance": 500, "merchantId": "mrc_xyz789" } ]

Get Balance for Specific Currency

async function getBalanceForCurrency(token, orgId, merchantId, currencySymbol) { const balances = await getMerchantBalances(token, orgId, merchantId); return balances.find(b => b.currencySymbol === currencySymbol); } // Usage const copBalance = await getBalanceForCurrency(token, orgId, merchantId, 'COP'); console.log('COP available:', copBalance.availableBalance);

Balance Operations

Crediting (Adding Funds)

Funds are automatically credited to virtual accounts through:

  1. PAYIN orders (customer payments)
// Customer pays 50,000 COP // Virtual Account COP: +50,000
  1. OFFRAMP orders (selling crypto)
// Sell 10 USDC for COP // Virtual Account COP: +8,000 (example rate)

Debiting (Removing Funds)

Funds are automatically debited from virtual accounts through:

  1. PAYOUT orders (provider payments)
// Pay provider 100,000 COP // Virtual Account COP: -100,000
  1. BALANCE_TRANSFER orders (currency exchange)
// Convert 1,000,000 COP to USD // Virtual Account COP: -1,000,000 // Virtual Account USD: +1,250 (example rate)
  1. ONRAMP orders (buying crypto)
// Buy 10 USDC with COP // Virtual Account COP: -8,000 (example rate)

Balance Validation

Before Creating PAYOUT Orders

Always check available balance before creating a PAYOUT:

async function safeCreatePayout(token, orgId, merchantId, payoutAmount, currency) { // 1. Get current balance const balance = await getBalanceForCurrency(token, orgId, merchantId, currency); // 2. Validate sufficient funds if (balance.availableBalance < payoutAmount) { throw new Error( `Insufficient balance: ${balance.availableBalance} ${currency} available, ` + `${payoutAmount} ${currency} required` ); } // 3. Create payout order const order = await createPayoutOrder(token, orgId, merchantId, { amount: payoutAmount, currency: currency, // ... other fields }); return order; } // Usage try { const payout = await safeCreatePayout(token, orgId, merchantId, 100000, 'COP'); console.log('Payout created:', payout.id); } catch (error) { console.error('Error:', error.message); }

Fund Settlement and Long-Term Holdings

Automatic Settlement: Funds held in virtual accounts cannot remain indefinitely. After a certain number of days, balances are automatically settled to your merchant’s registered bank account.

Settlement Behavior

Virtual accounts are designed for active payment operations, not long-term fund storage:

What happens during settlement:

  • Funds are transferred to your merchant’s registered bank account
  • Virtual account balance returns to zero
  • You receive a notification about the settlement
  • Transaction history is maintained for reconciliation

Alternative: Digital Dollar Holdings (USDC)

Hold Funds Long-Term: Instead of waiting for automatic settlement, you can convert your fiat balance to USDC (digital dollars) and hold funds in embedded crypto wallets indefinitely.

Benefits of USDC Holdings:

  • No automatic settlement - hold funds as long as needed
  • Stable value - pegged 1:1 to US Dollar
  • Instant liquidity - convert back to fiat anytime
  • Lower fees - blockchain-based transfers
  • Global access - use across borders

Converting Fiat to USDC

async function convertToUSDC(token, orgId, merchantId, amount, currency) { // 1. Get quote for ONRAMP (fiat to crypto) const quote = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes`, { type: 'ONRAMP', originCurrencySymbol: currency, destinationCurrencySymbol: 'USDC', amountIn: amount }, { headers: { 'Authorization': `Bearer ${token}` } } ); console.log(`Converting ${amount} ${currency} → ${quote.data.amountOut} USDC`); // 2. Create deal (not order!) const deal = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/deals`, { type: 'ONRAMP', destinationAccountId: 'wallet_abc123', // Your embedded wallet quoteId: quote.data.id }, { headers: { 'Authorization': `Bearer ${token}` } } ); return deal.data; } // Usage: Convert COP balance to USDC const usdcDeal = await convertToUSDC(token, orgId, merchantId, 1000000, 'COP'); console.log('Deal created:', usdcDeal.id); console.log('Funds will be held as USDC in wallet:', usdcDeal.destinationAccountId);
def convert_to_usdc(token, org_id, merchant_id, amount, currency): # Get quote quote_response = requests.post( f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/quotes', json={ 'type': 'ONRAMP', 'originCurrencySymbol': currency, 'destinationCurrencySymbol': 'USDC', 'amountIn': amount }, headers={'Authorization': f'Bearer {token}'} ) quote = quote_response.json() print(f"Converting {amount} {currency}{quote['amountOut']} USDC") # Create deal (not order!) deal_response = requests.post( f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/deals', json={ 'type': 'ONRAMP', 'destinationAccountId': 'wallet_abc123', 'quoteId': quote['id'] }, headers={'Authorization': f'Bearer {token}'} ) return deal_response.json() # Usage usdc_deal = convert_to_usdc(token, org_id, merchant_id, 1000000, 'COP') print(f"Deal created: {usdc_deal['id']}") print(f"Funds will be held as USDC in wallet: {usdc_deal['destinationAccountId']}")

Converting USDC Back to Fiat

When you need the funds back in fiat:

async function convertUSDCToFiat(token, orgId, merchantId, usdcAmount, targetCurrency) { // 1. Get quote for OFFRAMP (crypto to fiat) const quote = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes`, { type: 'OFFRAMP', originCurrencySymbol: 'USDC', destinationCurrencySymbol: targetCurrency, amountIn: usdcAmount }, { headers: { 'Authorization': `Bearer ${token}` } } ); // 2. Create deal (must be paid completely) const deal = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/deals`, { type: 'OFFRAMP', destinationAccountId: 'va_cop_12345', // Target virtual account quoteId: quote.data.id }, { headers: { 'Authorization': `Bearer ${token}` } } ); return deal.data; } // Usage: Convert USDC back to COP const copDeal = await convertUSDCToFiat(token, orgId, merchantId, 250, 'COP'); console.log('Deal created:', copDeal.id); console.log('USDC will be converted to COP in virtual account');

Recommended Strategy:

  • Keep operational funds in virtual accounts for daily PAYIN/PAYOUT operations
  • Convert excess balances to USDC for long-term holdings
  • Convert USDC back to fiat when you need to make large payouts or withdrawals

Fund Management Decision Tree

Learn more about ONRAMP (Buying Crypto) →

Learn more about OFFRAMP (Selling Crypto) →


Multi-Currency Management

Supported Currencies

Virtual accounts are automatically created for:

CurrencySymbolRegion
Colombian PesoCOPColombia
Brazilian RealBRLBrazil
Mexican PesoMXNMexico
Chilean PesoCLPChile
Argentine PesoARSArgentina
Peruvian SolPENPeru
US DollarUSDInternational
EuroEURInternational

Currency Exchange via BALANCE_TRANSFER

Transfer funds between currencies instantly:

async function transferBetweenCurrencies(token, orgId, merchantId) { // 1. Get quote const quote = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes`, { type: 'BALANCE_TRANSFER', originCurrencySymbol: 'COP', destinationCurrencySymbol: 'USD', amountIn: 1000000 // 1,000,000 COP }, { headers: { 'Authorization': `Bearer ${token}` } } ); console.log('Exchange rate:', quote.data.exchangeRate); console.log('Will receive:', quote.data.amountOut, 'USD'); // 2. Create transfer order const order = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`, { type: 'BALANCE_TRANSFER', originCurrencySymbol: 'COP', destinationCurrencySymbol: 'USD', amountIn: 1000000, quoteId: quote.data.id }, { headers: { 'Authorization': `Bearer ${token}` } } ); return order.data; }

Learn more about BALANCE_TRANSFER →


Balance History and Reconciliation

Tracking Balance Changes

Monitor balance changes through order history:

async function getBalanceHistory(token, orgId, merchantId, startDate, endDate) { const response = await axios.get( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`, { params: { startDate: startDate, endDate: endDate, limit: 100 }, headers: { 'Authorization': `Bearer ${token}` } } ); // Filter orders that affect balance return response.data.filter(order => ['PAYIN', 'PAYOUT', 'BALANCE_TRANSFER', 'ONRAMP', 'OFFRAMP'].includes(order.type) && order.status === 'COMPLETED' ); } // Usage const history = await getBalanceHistory( token, orgId, merchantId, '2025-01-01', '2025-01-31' ); let totalIn = 0; let totalOut = 0; history.forEach(order => { if (['PAYIN', 'OFFRAMP'].includes(order.type)) { totalIn += order.amountOut; } else if (['PAYOUT', 'BALANCE_TRANSFER', 'ONRAMP'].includes(order.type)) { totalOut += order.amountIn; } }); console.log('Total IN:', totalIn); console.log('Total OUT:', totalOut); console.log('Net:', totalIn - totalOut);

Best Practices

Balance Management

Do:

  • Check balances before creating PAYOUT orders
  • Monitor pending balances for incoming payments
  • Set up alerts for low balances
  • Reconcile regularly with your accounting system

Don’t:

  • Assume instant availability of PAYIN funds (check status)
  • Create PAYOUT orders exceeding available balance
  • Ignore reserved balances in your calculations

Currency Strategy

Multi-Currency Operations: Keep balances in the currencies you operate in most frequently to minimize conversion fees and exchange rate exposure.

Example strategy:

  • Operate mainly in Colombia → Keep most funds in COP
  • Pay international providers → Keep some USD balance
  • Occasional Brazilian sales → Convert BRL to COP as needed

Common Scenarios

Scenario 1: Customer Payment Flow

1. Customer initiates payment: 50,000 COP 2. PAYIN order created: Status PENDING 3. Virtual Account COP: pending +50,000 4. Customer completes payment 5. Order status: PAID 6. Virtual Account COP: available +50,000 7. Your application receives webhook: order.completed

Scenario 2: Provider Payment Flow

1. Check Virtual Account USD: 500 available 2. Create PAYOUT: 100 USD to provider 3. Virtual Account USD: reserved +100, available -100 4. Bank transfer initiated 5. Bank confirms transfer 6. Order status: COMPLETED 7. Virtual Account USD: reserved -100 (now only 400 total)

Scenario 3: Currency Exchange

1. Virtual Account COP: 1,000,000 available 2. Virtual Account USD: 0 available 3. Create BALANCE_TRANSFER: 1,000,000 COP → USD 4. Exchange rate: 4,000 COP per USD 5. Virtual Account COP: -1,000,000 6. Virtual Account USD: +250 7. Order completes instantly

Next Steps

Last updated on