Koywe
On this page

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

Drawing diagram…

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.

JavaScript
{
  "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.

JavaScript
{
  "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.

JavaScript
{
  "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`);
});

Response:

JSON
[
  {
    "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

JavaScript
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)
JavaScript
// Customer pays 50,000 COP
// Virtual Account COP: +50,000
  1. OFFRAMP orders (selling crypto)
JavaScript
// 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)
JavaScript
// Pay provider 100,000 COP
// Virtual Account COP: -100,000
  1. BALANCE_TRANSFER orders (currency exchange)
JavaScript
// 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)
JavaScript
// 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:

JavaScript
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:

Drawing diagram…

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`,
    {
      orderType: 'ONRAMP', executable: true,
      originCurrencySymbol: currency,
      destinationCurrencySymbol: 'USDC',
      amountIn: amount,
      network: 'ETHEREUM'  // Required for ONRAMP/OFFRAMP
    },
    { headers: { 'Authorization': `Bearer ${token}` } }
  );
  
  console.log(`Converting ${amount} ${currency} → ${quote.data.finalAmountOut} USDC`);
  
  // 2. Create deal (not order!)
  const deal = await axios.post(
    `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/deals`,
    {
      // Id of your CRYPTO account in K3 — not a wallet address
      destinationAccountId: 'acc_77a80cf9-496c-4d72-9c33-fca3c8d5bcfe',
      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 account:', usdcDeal.destinationAccountId);

Converting USDC Back to Fiat

When you need the funds back in fiat:

JavaScript
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`,
    {
      orderType: 'OFFRAMP', executable: true,
      originCurrencySymbol: 'USDC',
      destinationCurrencySymbol: targetCurrency,
      amountIn: usdcAmount,
      network: 'ETHEREUM'  // Required for ONRAMP/OFFRAMP
    },
    { 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`,
    {
      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

Drawing diagram…

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:

Drawing diagram…
JavaScript
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`,
    {
      orderType: 'BALANCE_TRANSFER', executable: true,
      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.finalAmountOut, '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:

JavaScript
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

Text
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

Text
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

Text
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