Core Concepts

Merchant External Accounts

Setting up bank and crypto accounts for receiving funds

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

External 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.

1async function addMerchantBankAccount(token, orgId, merchantId) {
2 const response = await axios.post(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/bank-accounts`,
4 {
5 country: 'CO',
6 currency: 'COP',
7 bankCode: 'BANCOLOMBIA',
8 accountNumber: '1234567890',
9 accountType: 'checking', // or 'savings'
10 isPrimary: true // Set as default settlement account
11 },
12 { headers: { 'Authorization': `Bearer ${token}` } }
13 );
14
15 console.log('Bank account added:', response.data.id);
16 console.log('Status:', response.data.status); // "PENDING_VERIFICATION"
17
18 return response.data;
19}
20
21const bankAccount = await addMerchantBankAccount(token, orgId, merchantId);

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 🇨🇱

1{
2 country: 'CL',
3 currency: 'CLP',
4 bankCode: 'BANCO_CHILE', // Required: BANCO_CHILE, BCI, SANTANDER_CHILE, etc.
5 accountNumber: '12345678' // Required
6}

Validations: Bank code must be in the valid banks list.


Colombia 🇨🇴

1{
2 country: 'CO',
3 currency: 'COP',
4 bankCode: 'BANCOLOMBIA', // Required: BANCOLOMBIA, DAVIVIENDA, BOGOTA, etc.
5 accountNumber: '1234567890', // Required
6 accountType: 'checking' // Required: 'checking' or 'savings'
7}

Validations: Account type must be supported by the bank. Account number length validated (min/max).


Brazil 🇧🇷

1{
2 country: 'BR',
3 currency: 'BRL',
4 accountNumber: '12345-6', // Required (OR pixKey)
5 // bankCode: '001' // Optional (deduced from accountNumber)
6}

Validations: Bank code deduced from account number. PIX key can be used instead of account number.


Mexico 🇲🇽

1{
2 country: 'MX',
3 currency: 'MXN',
4 accountNumber: '012345678901234567' // Required: CLABE (18 digits)
5 // bankCode: 'BBVA_MEXICO' // Optional (deduced from accountNumber)
6}

Validations: CLABE format validation (18 digits). Check digit validation. Bank deduced from CLABE.


Argentina 🇦🇷

1{
2 country: 'AR',
3 currency: 'ARS',
4 accountNumber: '0123456789012345678901', // Required: CVU or CBU or alias
5 // bankCode: 'BANCO_NACION' // Optional (deduced from accountNumber)
6}

Validations: CVU/CBU format validation. Check digit validation. Bank deduced from account number. Alias supported.


Peru 🇵🇪

1{
2 country: 'PE',
3 currency: 'PEN',
4 accountNumber: '01234567890123456789', // Required: CCI format
5 // bankCode: 'BCP' // Optional (deduced from accountNumber)
6}

Validations: CCI format validation (20 digits). Bank deduced from account number.


Bolivia 🇧🇴

1{
2 country: 'BO',
3 currency: 'BOB',
4 accountNumber: '1234567890' // Required
5 // bankCode: 'BNB' // Optional (deduced from accountNumber)
6}

Validations: Bank deduced from account number.


United States 🇺🇸

1{
2 country: 'US',
3 currency: 'USD',
4 accountNumber: '123456789', // Required
5 routingNumber: '021000021' // Required
6 // bankCode: 'CHASE' // Optional (deduced from routingNumber)
7}

Validations: Bank deduced from routing number. Generic bank saved if not found.

Verification Process

1

Account Created

External bank account is created with status PENDING_VERIFICATION

2

Microdeposit Sent (if required)

Koywe may send a small test deposit to verify account ownership

3

Verification Completed

You verify the deposit amount or account is auto-verified

4

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

Node.js
1async function getMerchantBankAccounts(token, orgId, merchantId) {
2 const response = await axios.get(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/bank-accounts`,
4 { headers: { 'Authorization': `Bearer ${token}` } }
5 );
6
7 console.log('External bank accounts:');
8 response.data.forEach(account => {
9 console.log(`- ${account.currency}: ${account.bankCode} ****${account.accountNumber.slice(-4)}`);
10 console.log(` Primary: ${account.isPrimary}, Status: ${account.status}`);
11 });
12
13 return response.data;
14}
15
16const 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

1async function addExternalCryptoWallet(token, orgId, merchantId, walletData) {
2 const response = await axios.post(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/external-wallets`,
4 {
5 name: walletData.name, // e.g., "Company Ledger Wallet"
6 currency: walletData.currency, // USDC, USDT, BTC, ETH, etc.
7 network: walletData.network, // ETHEREUM, POLYGON, BITCOIN, etc.
8 address: walletData.address, // Your wallet address
9 isPrimary: walletData.isPrimary || false
10 },
11 { headers: { 'Authorization': `Bearer ${token}` } }
12 );
13
14 console.log('External wallet added:', response.data.id);
15 console.log('Address:', response.data.address);
16 console.log('Network:', response.data.network);
17
18 return response.data;
19}
20
21// Example: Add USDC on Ethereum
22const usdcWallet = await addExternalCryptoWallet(token, orgId, merchantId, {
23 name: 'Company Ledger - USDC',
24 currency: 'USDC',
25 network: 'ETHEREUM',
26 address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb2',
27 isPrimary: true
28});
29
30console.log('USDC 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

Node.js
1async function getExternalWallets(token, orgId, merchantId) {
2 const response = await axios.get(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/external-wallets`,
4 { headers: { 'Authorization': `Bearer ${token}` } }
5 );
6
7 console.log('External crypto wallets:');
8 response.data.forEach(wallet => {
9 console.log(`- ${wallet.currency} (${wallet.network})`);
10 console.log(` Address: ${wallet.address}`);
11 console.log(` Primary: ${wallet.isPrimary}, Status: ${wallet.status}`);
12 });
13
14 return response.data;
15}
16
17const 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:

Node.js
1// 1. Get quote
2const quote = await axios.post(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes`,
4 {
5 type: 'ONRAMP',
6 originCurrencySymbol: 'COP',
7 destinationCurrencySymbol: 'USDC',
8 amountIn: 1000000, // 1M COP
9 network: 'ETHEREUM'
10 },
11 { headers: { 'Authorization': `Bearer ${token}` } }
12);
13
14// 2. Create deal with external wallet as destination
15const deal = await axios.post(
16 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/deals`,
17 {
18 type: 'ONRAMP',
19 destinationAccountId: usdcWallet.id, // Your external wallet ID
20 quoteId: quote.data.id
21 },
22 { headers: { 'Authorization': `Bearer ${token}` } }
23);
24
25console.log('ONRAMP deal created');
26console.log('Crypto will be sent to:', usdcWallet.address);
27console.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:

Node.js
1async function withdrawToExternalBank(token, orgId, merchantId, withdrawalData) {
2 // Check virtual account balance first
3 const balances = await axios.get(
4 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/bank-accounts/balances`,
5 { headers: { 'Authorization': `Bearer ${token}` } }
6 );
7
8 const copBalance = balances.data.find(b => b.currencySymbol === 'COP');
9
10 if (copBalance.availableBalance < withdrawalData.amount) {
11 throw new Error('Insufficient balance for withdrawal');
12 }
13
14 // Create withdrawal order
15 const withdrawal = await axios.post(
16 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/withdrawals`,
17 {
18 currency: 'COP',
19 amount: withdrawalData.amount,
20 destinationAccountId: withdrawalData.bankAccountId, // Your external bank account
21 description: 'Withdrawal to company account'
22 },
23 { headers: { 'Authorization': `Bearer ${token}` } }
24 );
25
26 console.log('Withdrawal initiated:', withdrawal.data.id);
27 console.log('Amount:', withdrawal.data.amount, 'COP');
28 console.log('Status:', withdrawal.data.status); // "PROCESSING"
29 console.log('ETA:', withdrawal.data.estimatedSettlement);
30
31 return withdrawal.data;
32}
33
34const withdrawal = await withdrawToExternalBank(token, orgId, merchantId, {
35 amount: 5000000, // 5M COP
36 bankAccountId: bankAccount.id
37});

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

Verify 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.

Monitor Webhooks

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

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

1// Setup external bank accounts for each currency
2const copAccount = await axios.post(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/bank-accounts`,
4 {
5 country: 'CO',
6 currency: 'COP',
7 bankCode: 'BANCOLOMBIA',
8 accountNumber: '1234567890',
9 accountType: 'checking',
10 isPrimary: true
11 },
12 { headers: { 'Authorization': `Bearer ${token}` } }
13);
14
15const brlAccount = await axios.post(
16 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/bank-accounts`,
17 {
18 country: 'BR',
19 currency: 'BRL',
20 accountNumber: '12345-6', // Bank deduced automatically
21 isPrimary: false
22 },
23 { headers: { 'Authorization': `Bearer ${token}` } }
24);
25
26const mxnAccount = await axios.post(
27 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/bank-accounts`,
28 {
29 country: 'MX',
30 currency: 'MXN',
31 accountNumber: '012345678901234567', // CLABE - bank deduced
32 isPrimary: false
33 },
34 { headers: { 'Authorization': `Bearer ${token}` } }
35);
36
37console.log('Multi-currency external accounts configured');
38console.log('All accounts auto-linked to merchant business information');

Multi-Network Crypto Setup

For diversified crypto holdings:

1// USDC on multiple networks
2const usdcEthereum = await addExternalCryptoWallet(token, orgId, merchantId, {
3 name: 'USDC - Ethereum Main',
4 currency: 'USDC',
5 network: 'ETHEREUM',
6 address: '0x...',
7 isPrimary: true
8});
9
10const usdcPolygon = await addExternalCryptoWallet(token, orgId, merchantId, {
11 name: 'USDC - Polygon Low Fees',
12 currency: 'USDC',
13 network: 'POLYGON',
14 address: '0x...',
15 isPrimary: false
16});
17
18// Different stablecoins
19const usdtTron = await addExternalCryptoWallet(token, orgId, merchantId, {
20 name: 'USDT - Tron',
21 currency: 'USDT',
22 network: 'TRON',
23 address: 'T...',
24 isPrimary: false
25});
26
27console.log('Multi-network external wallets configured');

Next Steps