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:
- PAYIN orders (customer payments)
// Customer pays 50,000 COP
// Virtual Account COP: +50,000- 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:
- PAYOUT orders (provider payments)
// Pay provider 100,000 COP
// Virtual Account COP: -100,000- 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)- 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:
| Currency | Symbol | Region |
|---|---|---|
| Colombian Peso | COP | Colombia |
| Brazilian Real | BRL | Brazil |
| Mexican Peso | MXN | Mexico |
| Chilean Peso | CLP | Chile |
| Argentine Peso | ARS | Argentina |
| Peruvian Sol | PEN | Peru |
| US Dollar | USD | International |
| Euro | EUR | International |
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.completedScenario 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