Skip to Content
Core ConceptsMerchant External Accounts

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

building-columnsExternal Bank Accounts

Your company bank accounts for receiving fiat settlements and withdrawals

👛External Crypto Wallets

Your crypto wallet addresses for receiving cryptocurrency from ONRAMP deals


External Bank Accounts vs Virtual Accounts

Understanding the difference:

FeatureVirtual AccountExternal Bank Account
LocationWithin KoyweYour own bank
PurposeOperations & transactionsFinal settlement destination
SetupAutomaticManual configuration
UsagePAYINs, PAYOUTs, transfersWithdrawals, settlements
CurrenciesMultiple supportedBank-specific
FeesNone for internal opsMay 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

CurrencySupported NetworksNotes
USDCEthereum, Polygon, BSCMost popular stablecoin
USDTEthereum, Polygon, BSC, TronMulti-chain support
ETHEthereumNative Ethereum
BTCBitcoinBitcoin mainnet only
MATICPolygonNative Polygon
BNBBSCNative 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

shield-checkVerify Addresses

Always double-check crypto wallet addresses before adding them. Errors are irreversible.

🧪Use Test Amounts

Send small test transactions to new external wallets before large transfers.

bellMonitor Webhooks

Set up webhooks to track settlements and withdrawals to external accounts.

file-linesKeep Records

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');

Next Steps

Last updated on