Core Concepts

Virtual Accounts

Managing multi-currency balances

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.

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

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.

1{
2 "pendingBalance": 50000, // 50,000 COP pending
3 "currencySymbol": "COP"
4}

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.

1{
2 "reservedBalance": 25000, // 25,000 COP reserved
3 "currencySymbol": "COP"
4}

Examples:

  • PAYOUT order in progress
  • BALANCE_TRANSFER being executed

Checking Balances

Get All Balances for a Merchant

1async function getMerchantBalances(token, orgId, merchantId) {
2 const response = await axios.get(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/bank-accounts/balances`,
4 {
5 headers: { 'Authorization': `Bearer ${token}` }
6 }
7 );
8
9 return response.data;
10}
11
12// Usage
13const balances = await getMerchantBalances(token, orgId, merchantId);
14
15balances.forEach(balance => {
16 console.log(`${balance.currencySymbol}: ${balance.availableBalance} available`);
17});

Response:

1[
2 {
3 "id": "va_cop_12345",
4 "currencySymbol": "COP",
5 "availableBalance": 1000000,
6 "pendingBalance": 50000,
7 "reservedBalance": 25000,
8 "totalBalance": 1075000,
9 "merchantId": "mrc_xyz789"
10 },
11 {
12 "id": "va_usd_67890",
13 "currencySymbol": "USD",
14 "availableBalance": 500,
15 "pendingBalance": 0,
16 "reservedBalance": 0,
17 "totalBalance": 500,
18 "merchantId": "mrc_xyz789"
19 }
20]

Get Balance for Specific Currency

Node.js
1async function getBalanceForCurrency(token, orgId, merchantId, currencySymbol) {
2 const balances = await getMerchantBalances(token, orgId, merchantId);
3 return balances.find(b => b.currencySymbol === currencySymbol);
4}
5
6// Usage
7const copBalance = await getBalanceForCurrency(token, orgId, merchantId, 'COP');
8console.log('COP available:', copBalance.availableBalance);

Balance Operations

Crediting (Adding Funds)

Funds are automatically credited to virtual accounts through:

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

Debiting (Removing Funds)

Funds are automatically debited from virtual accounts through:

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

Balance Validation

Before Creating PAYOUT Orders

Always check available balance before creating a PAYOUT:

Node.js
1async function safeCreatePayout(token, orgId, merchantId, payoutAmount, currency) {
2 // 1. Get current balance
3 const balance = await getBalanceForCurrency(token, orgId, merchantId, currency);
4
5 // 2. Validate sufficient funds
6 if (balance.availableBalance < payoutAmount) {
7 throw new Error(
8 `Insufficient balance: ${balance.availableBalance} ${currency} available, ` +
9 `${payoutAmount} ${currency} required`
10 );
11 }
12
13 // 3. Create payout order
14 const order = await createPayoutOrder(token, orgId, merchantId, {
15 amount: payoutAmount,
16 currency: currency,
17 // ... other fields
18 });
19
20 return order;
21}
22
23// Usage
24try {
25 const payout = await safeCreatePayout(token, orgId, merchantId, 100000, 'COP');
26 console.log('Payout created:', payout.id);
27} catch (error) {
28 console.error('Error:', error.message);
29}

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

1async function convertToUSDC(token, orgId, merchantId, amount, currency) {
2 // 1. Get quote for ONRAMP (fiat to crypto)
3 const quote = await axios.post(
4 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes`,
5 {
6 type: 'ONRAMP',
7 originCurrencySymbol: currency,
8 destinationCurrencySymbol: 'USDC',
9 amountIn: amount
10 },
11 { headers: { 'Authorization': `Bearer ${token}` } }
12 );
13
14 console.log(`Converting ${amount} ${currency} โ†’ ${quote.data.amountOut} USDC`);
15
16 // 2. Create deal (not order!)
17 const deal = await axios.post(
18 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/deals`,
19 {
20 type: 'ONRAMP',
21 destinationAccountId: 'wallet_abc123', // Your embedded wallet
22 quoteId: quote.data.id
23 },
24 { headers: { 'Authorization': `Bearer ${token}` } }
25 );
26
27 return deal.data;
28}
29
30// Usage: Convert COP balance to USDC
31const usdcDeal = await convertToUSDC(token, orgId, merchantId, 1000000, 'COP');
32console.log('Deal created:', usdcDeal.id);
33console.log('Funds will be held as USDC in wallet:', usdcDeal.destinationAccountId);

Converting USDC Back to Fiat

When you need the funds back in fiat:

Node.js
1async function convertUSDCToFiat(token, orgId, merchantId, usdcAmount, targetCurrency) {
2 // 1. Get quote for OFFRAMP (crypto to fiat)
3 const quote = await axios.post(
4 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes`,
5 {
6 type: 'OFFRAMP',
7 originCurrencySymbol: 'USDC',
8 destinationCurrencySymbol: targetCurrency,
9 amountIn: usdcAmount
10 },
11 { headers: { 'Authorization': `Bearer ${token}` } }
12 );
13
14 // 2. Create deal (must be paid completely)
15 const deal = await axios.post(
16 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/deals`,
17 {
18 type: 'OFFRAMP',
19 destinationAccountId: 'va_cop_12345', // Target virtual account
20 quoteId: quote.data.id
21 },
22 { headers: { 'Authorization': `Bearer ${token}` } }
23 );
24
25 return deal.data;
26}
27
28// Usage: Convert USDC back to COP
29const copDeal = await convertUSDCToFiat(token, orgId, merchantId, 250, 'COP');
30console.log('Deal created:', copDeal.id);
31console.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:

Node.js
1async function transferBetweenCurrencies(token, orgId, merchantId) {
2 // 1. Get quote
3 const quote = await axios.post(
4 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes`,
5 {
6 type: 'BALANCE_TRANSFER',
7 originCurrencySymbol: 'COP',
8 destinationCurrencySymbol: 'USD',
9 amountIn: 1000000 // 1,000,000 COP
10 },
11 { headers: { 'Authorization': `Bearer ${token}` } }
12 );
13
14 console.log('Exchange rate:', quote.data.exchangeRate);
15 console.log('Will receive:', quote.data.amountOut, 'USD');
16
17 // 2. Create transfer order
18 const order = await axios.post(
19 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`,
20 {
21 type: 'BALANCE_TRANSFER',
22 originCurrencySymbol: 'COP',
23 destinationCurrencySymbol: 'USD',
24 amountIn: 1000000,
25 quoteId: quote.data.id
26 },
27 { headers: { 'Authorization': `Bearer ${token}` } }
28 );
29
30 return order.data;
31}

Learn more about BALANCE_TRANSFER โ†’


Balance History and Reconciliation

Tracking Balance Changes

Monitor balance changes through order history:

Node.js
1async function getBalanceHistory(token, orgId, merchantId, startDate, endDate) {
2 const response = await axios.get(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`,
4 {
5 params: {
6 startDate: startDate,
7 endDate: endDate,
8 limit: 100
9 },
10 headers: { 'Authorization': `Bearer ${token}` }
11 }
12 );
13
14 // Filter orders that affect balance
15 return response.data.filter(order =>
16 ['PAYIN', 'PAYOUT', 'BALANCE_TRANSFER', 'ONRAMP', 'OFFRAMP'].includes(order.type) &&
17 order.status === 'COMPLETED'
18 );
19}
20
21// Usage
22const history = await getBalanceHistory(
23 token,
24 orgId,
25 merchantId,
26 '2025-01-01',
27 '2025-01-31'
28);
29
30let totalIn = 0;
31let totalOut = 0;
32
33history.forEach(order => {
34 if (['PAYIN', 'OFFRAMP'].includes(order.type)) {
35 totalIn += order.amountOut;
36 } else if (['PAYOUT', 'BALANCE_TRANSFER', 'ONRAMP'].includes(order.type)) {
37 totalOut += order.amountIn;
38 }
39});
40
41console.log('Total IN:', totalIn);
42console.log('Total OUT:', totalOut);
43console.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