Merchant External Accounts
Configure your merchant’s external bank accounts and crypto wallets to receive funds from virtual account settlements and crypto purchases.
What are Merchant External Accounts?
Merchant External Accounts are your merchant’s own bank accounts and crypto wallets where you receive funds from Koywe operations.
Types of External Accounts
Your company bank accounts for receiving fiat settlements and withdrawals
Your crypto wallet addresses for receiving cryptocurrency from ONRAMP deals
External Bank Accounts vs Virtual Accounts
Understanding the difference:
| Feature | Virtual Account | External Bank Account |
|---|---|---|
| Location | Within Koywe | Your own bank |
| Purpose | Operations & transactions | Final settlement destination |
| Setup | Automatic | Manual configuration |
| Usage | PAYINs, PAYOUTs, transfers | Withdrawals, settlements |
| Currencies | Multiple supported | Bank-specific |
| Fees | None for internal ops | May have transfer fees |
Automatic Settlement: Funds in virtual accounts are automatically settled to your registered external bank account after a configured period (typically 1-7 days). You can also manually request withdrawals.
Setting Up External Bank Accounts
When You Need an External Bank Account
✅ Required for:
- Receiving automatic settlements from virtual accounts
- Withdrawing funds from virtual balances
- Receiving merchant payouts
- Compliance and verification
Creating an External Bank Account
Automatic Association: Bank accounts are automatically associated with your merchant’s registered business information. No need to send holder name or document numbers - we use the information already in the system.
Unified Endpoint: Both bank accounts and crypto wallets use the same /accounts endpoint. The kind field determines the account type: "BANK" for fiat bank accounts, "CRYPTO" for crypto wallets.
async function addMerchantBankAccount(token, orgId, merchantId) {
const response = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts`,
{
name: 'Company Settlement Account',
kind: 'BANK', // Indicates this is a fiat bank account
countrySymbol: 'CO',
currencySymbol: 'COP',
entity: 'BANCOLOMBIA', // Bank code
accountNumber: '1234567890',
type: 'CHECKING', // or 'SAVINGS'
isDefault: true, // Set as default settlement account
isVirtual: false // External account (not Koywe-managed)
},
{ headers: { 'Authorization': `Bearer ${token}` } }
);
console.log('Bank account added:', response.data.id);
console.log('Status:', response.data.status);
return response.data;
}
const bankAccount = await addMerchantBankAccount(token, orgId, merchantId);def add_merchant_bank_account(token, org_id, merchant_id):
response = requests.post(
f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/accounts',
headers={'Authorization': f'Bearer {token}'},
json={
'name': 'Company Settlement Account',
'kind': 'BANK', # Indicates this is a fiat bank account
'countrySymbol': 'CO',
'currencySymbol': 'COP',
'entity': 'BANCOLOMBIA', # Bank code
'accountNumber': '1234567890',
'type': 'CHECKING', # or 'SAVINGS'
'isDefault': True, # Set as default settlement account
'isVirtual': False # External account (not Koywe-managed)
}
)
response.raise_for_status()
bank_account = response.json()
print(f'Bank account added: {bank_account["id"]}')
print(f'Status: {bank_account["status"]}')
return bank_account
bank_account = add_merchant_bank_account(token, org_id, merchant_id)curl -X POST https://api-sandbox.koywe.com/api/v1/organizations/{orgId}/merchants/{merchantId}/accounts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Company Settlement Account",
"kind": "BANK",
"countrySymbol": "CO",
"currencySymbol": "COP",
"entity": "BANCOLOMBIA",
"accountNumber": "1234567890",
"type": "CHECKING",
"isDefault": true,
"isVirtual": false
}'Required Fields by Country
Bank Code Flexibility: For most countries, the bank code can be deduced from the account number. You can still provide it, but the API will validate against the deduced value. For countries where it’s required, validation ensures the bank is in the supported list.
Chile 🇨🇱
{
name: 'Chile Settlement Account',
kind: 'BANK',
countrySymbol: 'CL',
currencySymbol: 'CLP',
entity: 'BANCO_CHILE', // Required: BANCO_CHILE, BCI, SANTANDER_CHILE, etc.
accountNumber: '12345678', // Required
isDefault: true,
isVirtual: false
}Validations: Bank code (entity) must be in the valid banks list.
Colombia 🇨🇴
{
name: 'Colombia Settlement Account',
kind: 'BANK',
countrySymbol: 'CO',
currencySymbol: 'COP',
entity: 'BANCOLOMBIA', // Required: BANCOLOMBIA, DAVIVIENDA, BOGOTA, etc.
accountNumber: '1234567890', // Required
type: 'CHECKING', // Required: 'CHECKING' or 'SAVINGS'
isDefault: true,
isVirtual: false
}Validations: Account type must be supported by the bank. Account number length validated (min/max).
Brazil 🇧🇷
{
name: 'Brazil Settlement Account',
kind: 'BANK',
countrySymbol: 'BR',
currencySymbol: 'BRL',
accountNumber: '12345-6', // Required (OR pixKey)
// entity: '001' // Optional (deduced from accountNumber)
isDefault: true,
isVirtual: false
}Validations: Bank code deduced from account number. PIX key can be used instead of account number.
Mexico 🇲🇽
{
name: 'Mexico Settlement Account',
kind: 'BANK',
countrySymbol: 'MX',
currencySymbol: 'MXN',
accountNumber: '012345678901234567', // Required: CLABE (18 digits)
// entity: 'BBVA_MEXICO' // Optional (deduced from accountNumber)
isDefault: true,
isVirtual: false
}Validations: CLABE format validation (18 digits). Check digit validation. Bank deduced from CLABE.
Argentina 🇦🇷
{
name: 'Argentina Settlement Account',
kind: 'BANK',
countrySymbol: 'AR',
currencySymbol: 'ARS',
accountNumber: '0123456789012345678901', // Required: CVU or CBU or alias
// entity: 'BANCO_NACION' // Optional (deduced from accountNumber)
isDefault: true,
isVirtual: false
}Validations: CVU/CBU format validation. Check digit validation. Bank deduced from account number. Alias supported.
Peru 🇵🇪
{
name: 'Peru Settlement Account',
kind: 'BANK',
countrySymbol: 'PE',
currencySymbol: 'PEN',
accountNumber: '01234567890123456789', // Required: CCI format
// entity: 'BCP' // Optional (deduced from accountNumber)
isDefault: true,
isVirtual: false
}Validations: CCI format validation (20 digits). Bank deduced from account number.
Bolivia 🇧🇴
{
name: 'Bolivia Settlement Account',
kind: 'BANK',
countrySymbol: 'BO',
currencySymbol: 'BOB',
accountNumber: '1234567890', // Required
// entity: 'BNB' // Optional (deduced from accountNumber)
isDefault: true,
isVirtual: false
}Validations: Bank deduced from account number.
United States 🇺🇸
{
name: 'US Settlement Account',
kind: 'BANK',
countrySymbol: 'US',
currencySymbol: 'USD',
accountNumber: '123456789', // Required
routingNumber: '021000021', // Required
// entity: 'CHASE' // Optional (deduced from routingNumber)
isDefault: true,
isVirtual: false
}Validations: Bank deduced from routing number. Generic bank saved if not found.
Verification Process
Account Created
External bank account is created with status PENDING_VERIFICATION
Microdeposit Sent (if required)
Koywe may send a small test deposit to verify account ownership
Verification Completed
You verify the deposit amount or account is auto-verified
Account Activated
Status changes to VERIFIED and ready for settlements
Automatic Verification: The bank account is automatically linked to your merchant’s registered business information. Ensure your merchant profile has the correct legal name and tax ID, as these will be used for account verification.
Listing Merchant Bank Accounts
async function getMerchantBankAccounts(token, orgId, merchantId) {
const response = await axios.get(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts`,
{
params: { kind: 'BANK' }, // Filter for bank accounts only
headers: { 'Authorization': `Bearer ${token}` }
}
);
console.log('External bank accounts:');
response.data.forEach(account => {
console.log(`- ${account.currencySymbol}: ${account.entity} ****${account.accountNumber.slice(-4)}`);
console.log(` Default: ${account.isDefault}, Status: ${account.status}`);
});
return response.data;
}
const accounts = await getMerchantBankAccounts(token, orgId, merchantId);Setting Up External Crypto Wallets
When You Need an External Crypto Wallet
✅ Required for:
- Receiving cryptocurrency from ONRAMP deals
- Sending crypto to your own custody solutions
- Long-term crypto holdings outside Koywe
Embedded Wallets vs External Wallets: Koywe provides embedded wallets automatically (via /wallet endpoint). External wallets are YOUR OWN wallets on hardware devices, exchanges, or other custody solutions where you want to receive crypto.
Adding an External Crypto Wallet
Same Endpoint as Bank Accounts: External crypto wallets use the same /accounts endpoint as bank accounts. Set kind: "CRYPTO" to create a crypto wallet instead of a bank account.
async function addExternalCryptoWallet(token, orgId, merchantId, walletData) {
const response = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts`,
{
name: walletData.name, // e.g., "Company Ledger Wallet"
kind: 'CRYPTO', // Indicates this is a crypto wallet
currencySymbol: walletData.currency, // USDC, USDT, BTC, ETH, etc.
network: walletData.network, // ETHEREUM, POLYGON, BITCOIN, etc.
address: walletData.address, // Your wallet address
isDefault: walletData.isDefault || false,
isVirtual: false // External wallet (not Koywe-managed)
},
{ headers: { 'Authorization': `Bearer ${token}` } }
);
console.log('External wallet added:', response.data.id);
console.log('Address:', response.data.address);
console.log('Network:', response.data.network);
return response.data;
}
// Example: Add USDC on Ethereum
const usdcWallet = await addExternalCryptoWallet(token, orgId, merchantId, {
name: 'Company Ledger - USDC',
currency: 'USDC',
network: 'ETHEREUM',
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb2',
isDefault: true
});
console.log('USDC wallet registered for ONRAMP operations');def add_external_crypto_wallet(token, org_id, merchant_id, wallet_data):
response = requests.post(
f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/accounts',
headers={'Authorization': f'Bearer {token}'},
json={
'name': wallet_data['name'],
'kind': 'CRYPTO', # Indicates this is a crypto wallet
'currencySymbol': wallet_data['currency'],
'network': wallet_data['network'],
'address': wallet_data['address'],
'isDefault': wallet_data.get('is_default', False),
'isVirtual': False # External wallet (not Koywe-managed)
}
)
response.raise_for_status()
wallet = response.json()
print(f'External wallet added: {wallet["id"]}')
print(f'Address: {wallet["address"]}')
print(f'Network: {wallet["network"]}')
return wallet
# Example: Add USDT on Polygon
usdt_wallet = add_external_crypto_wallet(token, org_id, merchant_id, {
'name': 'Company Custody - USDT',
'currency': 'USDT',
'network': 'POLYGON',
'address': '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
'is_default': True
})
print('USDT wallet registered for ONRAMP operations')Supported Networks and Currencies
| Currency | Supported Networks | Notes |
|---|---|---|
| USDC | Ethereum, Polygon, BSC | Most popular stablecoin |
| USDT | Ethereum, Polygon, BSC, Tron | Multi-chain support |
| ETH | Ethereum | Native Ethereum |
| BTC | Bitcoin | Bitcoin mainnet only |
| MATIC | Polygon | Native Polygon |
| BNB | BSC | Native BSC |
Network Matching Critical: When creating ONRAMP deals, the network parameter must match one of your registered external wallet networks. Always verify the network before sending crypto!
Listing External Crypto Wallets
async function getExternalWallets(token, orgId, merchantId) {
const response = await axios.get(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts`,
{
params: { kind: 'CRYPTO' }, // Filter for crypto wallets only
headers: { 'Authorization': `Bearer ${token}` }
}
);
console.log('External crypto wallets:');
response.data.forEach(wallet => {
console.log(`- ${wallet.currencySymbol} (${wallet.network})`);
console.log(` Address: ${wallet.address}`);
console.log(` Default: ${wallet.isDefault}, Status: ${wallet.status}`);
});
return response.data;
}
const wallets = await getExternalWallets(token, orgId, merchantId);Using External Accounts in Operations
ONRAMP: Send Crypto to External Wallet
When creating ONRAMP deals, specify your external wallet as the destination:
// 1. Get quote
const quote = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes`,
{
type: 'ONRAMP',
originCurrencySymbol: 'COP',
destinationCurrencySymbol: 'USDC',
amountIn: 1000000, // 1M COP
network: 'ETHEREUM'
},
{ headers: { 'Authorization': `Bearer ${token}` } }
);
// 2. Create deal with external wallet as destination
const deal = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/deals`,
{
type: 'ONRAMP',
destinationAccountId: usdcWallet.id, // Your external wallet ID
quoteId: quote.data.id
},
{ headers: { 'Authorization': `Bearer ${token}` } }
);
console.log('ONRAMP deal created');
console.log('Crypto will be sent to:', usdcWallet.address);
console.log('Network:', usdcWallet.network);Use Embedded vs External: For operational crypto (frequent trading, OFFRAMP), use embedded wallets. For long-term holdings or specific custody requirements, use external wallets.
Withdrawals: Move Funds to External Bank Account
Request withdrawal from virtual account to your external bank account:
async function withdrawToExternalBank(token, orgId, merchantId, withdrawalData) {
// Check virtual account balance first
const balances = await axios.get(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts/balances`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
const copBalance = balances.data.find(b => b.currencySymbol === 'COP');
if (copBalance.availableBalance < withdrawalData.amount) {
throw new Error('Insufficient balance for withdrawal');
}
// Create withdrawal order
const withdrawal = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/withdrawals`,
{
currency: 'COP',
amount: withdrawalData.amount,
destinationAccountId: withdrawalData.bankAccountId, // Your external bank account
description: 'Withdrawal to company account'
},
{ headers: { 'Authorization': `Bearer ${token}` } }
);
console.log('Withdrawal initiated:', withdrawal.data.id);
console.log('Amount:', withdrawal.data.amount, 'COP');
console.log('Status:', withdrawal.data.status); // "PROCESSING"
console.log('ETA:', withdrawal.data.estimatedSettlement);
return withdrawal.data;
}
const withdrawal = await withdrawToExternalBank(token, orgId, merchantId, {
amount: 5000000, // 5M COP
bankAccountId: bankAccount.id
});Automatic Settlements: If you have automatic settlements enabled, funds will be transferred to your primary external bank account automatically after the configured holding period. Manual withdrawals give you more control over timing.
Best Practices
Security
Always double-check crypto wallet addresses before adding them. Errors are irreversible.
Send small test transactions to new external wallets before large transfers.
Set up webhooks to track settlements and withdrawals to external accounts.
Maintain records of all external accounts for compliance and auditing.
Account Management
✅ Do:
- Keep primary accounts up to date
- Verify new accounts immediately
- Use separate wallets per network
- Document account purposes internally
❌ Don’t:
- Share account credentials
- Use exchange deposit addresses as external wallets (use withdrawal addresses)
- Add unverified or test wallets in production
- Use personal accounts for business operations
Common Patterns
Multi-Currency Setup
For businesses operating in multiple countries:
// Setup external bank accounts for each currency
const copAccount = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts`,
{
country: 'CO',
currency: 'COP',
bankCode: 'BANCOLOMBIA',
accountNumber: '1234567890',
accountType: 'checking',
isPrimary: true
},
{ headers: { 'Authorization': `Bearer ${token}` } }
);
const brlAccount = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts`,
{
country: 'BR',
currency: 'BRL',
accountNumber: '12345-6', // Bank deduced automatically
isPrimary: false
},
{ headers: { 'Authorization': `Bearer ${token}` } }
);
const mxnAccount = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts`,
{
country: 'MX',
currency: 'MXN',
accountNumber: '012345678901234567', // CLABE - bank deduced
isPrimary: false
},
{ headers: { 'Authorization': `Bearer ${token}` } }
);
console.log('Multi-currency external accounts configured');
console.log('All accounts auto-linked to merchant business information');Multi-Network Crypto Setup
For diversified crypto holdings:
// USDC on multiple networks
const usdcEthereum = await addExternalCryptoWallet(token, orgId, merchantId, {
name: 'USDC - Ethereum Main',
currency: 'USDC',
network: 'ETHEREUM',
address: '0x...',
isPrimary: true
});
const usdcPolygon = await addExternalCryptoWallet(token, orgId, merchantId, {
name: 'USDC - Polygon Low Fees',
currency: 'USDC',
network: 'POLYGON',
address: '0x...',
isPrimary: false
});
// Different stablecoins
const usdtTron = await addExternalCryptoWallet(token, orgId, merchantId, {
name: 'USDT - Tron',
currency: 'USDT',
network: 'TRON',
address: 'T...',
isPrimary: false
});
console.log('Multi-network external wallets configured');