Skip to Content
Paying ProvidersIntegration Guide

Paying Providers Integration Guide

This guide walks you through implementing provider payouts in your application.

Prerequisites

  • API credentials (key, secret, organizationId, merchantId)
  • Funds in your virtual account
  • Provider information ready
  • Understanding of Virtual Accounts

Step 1: Check Virtual Account Balance

Always check balance before creating a payout:

async function getBalance(token, orgId, merchantId, currencySymbol) { const response = await axios.get( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/accounts/balances`, { headers: { 'Authorization': `Bearer ${token}` } } ); const balances = response.data; const balance = balances.find(b => b.currencySymbol === currencySymbol); return balance || { availableBalance: 0, currencySymbol }; } // Usage const balance = await getBalance(token, orgId, merchantId, 'COP'); console.log('Available balance:', balance.availableBalance, 'COP'); // Check if sufficient if (balance.availableBalance < 500000) { console.error('Insufficient balance for payout'); // Handle: add funds via PAYIN or show error }
def get_balance(token, org_id, merchant_id, currency_symbol): response = requests.get( f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/accounts/balances', headers={'Authorization': f'Bearer {token}'} ) response.raise_for_status() balances = response.json() balance = next((b for b in balances if b['currencySymbol'] == currency_symbol), None) return balance or {'availableBalance': 0, 'currencySymbol': currency_symbol} # Usage balance = get_balance(token, org_id, merchant_id, 'COP') print(f"Available: {balance['availableBalance']} COP")

Critical: If balance is insufficient, the PAYOUT order will fail. Always check balance first.


Step 2: Create Provider Contact

Store provider information:

async function createProviderContact(token, orgId, merchantId, providerData) { const response = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/contacts`, { email: providerData.email, phone: providerData.phone, fullName: providerData.fullName, countrySymbol: providerData.country, documentType: providerData.documentType, documentNumber: providerData.documentNumber, businessType: providerData.businessType // 'PERSON' or 'COMPANY' }, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } } ); return response.data; } // Usage - Company provider const provider = await createProviderContact(token, orgId, merchantId, { email: 'provider@example.com', phone: '+573001234567', fullName: 'Servicios ABC SAS', country: 'CO', documentType: 'NIT', // Tax ID for companies in Colombia documentNumber: '900123456-1', businessType: 'COMPANY' }); console.log('Provider contact created:', provider.id);
def create_provider_contact(token, org_id, merchant_id, provider_data): response = requests.post( f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/contacts', json={ 'email': provider_data['email'], 'phone': provider_data.get('phone'), 'fullName': provider_data['full_name'], 'countrySymbol': provider_data['country'], 'documentType': provider_data['document_type'], 'documentNumber': provider_data['document_number'], 'businessType': provider_data['business_type'] }, headers={ 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } ) response.raise_for_status() return response.json() # Usage provider = create_provider_contact(token, org_id, merchant_id, { 'email': 'provider@example.com', 'phone': '+573001234567', 'full_name': 'Servicios ABC SAS', 'country': 'CO', 'document_type': 'NIT', 'document_number': '900123456-1', 'business_type': 'COMPANY' })

Step 3: Add Provider Bank Account

Link the provider’s bank account details:

async function addProviderBankAccount(token, orgId, merchantId, contactId, bankData) { const response = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/contacts/${contactId}/accounts`, { countrySymbol: bankData.country, currencySymbol: bankData.currency, bankCode: bankData.bankCode, accountNumber: bankData.accountNumber, accountType: bankData.accountType, // 'checking' or 'savings' holderName: bankData.holderName }, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } } ); return response.data; } // Usage const bankAccount = await addProviderBankAccount(token, orgId, merchantId, provider.id, { country: 'CO', currency: 'COP', bankCode: 'BANCOLOMBIA', accountNumber: '1234567890', accountType: 'checking', holderName: 'Servicios ABC SAS' }); console.log('Bank account added:', bankAccount.id);
def add_provider_bank_account(token, org_id, merchant_id, contact_id, bank_data): response = requests.post( f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/contacts/{contact_id}/accounts', json={ 'countrySymbol': bank_data['country'], 'currencySymbol': bank_data['currency'], 'bankCode': bank_data['bank_code'], 'accountNumber': bank_data['account_number'], 'accountType': bank_data['account_type'], 'holderName': bank_data['holder_name'] }, headers={ 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } ) response.raise_for_status() return response.json() # Usage bank_account = add_provider_bank_account(token, org_id, merchant_id, provider['id'], { 'country': 'CO', 'currency': 'COP', 'bank_code': 'BANCOLOMBIA', 'account_number': '1234567890', 'account_type': 'checking', 'holder_name': 'Servicios ABC SAS' })

Bank Codes by Country

CountryExample BanksCode Format
ColombiaBANCOLOMBIA, DAVIVIENDA, BOGOTABank name
BrazilBANCO_DO_BRASIL, BRADESCO, ITAUBank code
MexicoBBVA_MEXICO, SANTANDER_MEXICOBank identifier
ChileBANCO_CHILE, BCI, SANTANDER_CHILEBank name

Complete bank codes reference →


Step 4: Create PAYOUT Order

Create the payout order:

async function createPayoutOrder(token, orgId, merchantId, payoutData) { const response = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`, { type: 'PAYOUT', originCurrencySymbol: payoutData.currency, destinationCurrencySymbol: payoutData.currency, // Same currency amountIn: payoutData.amount, contactId: payoutData.contactId, destinationAccountId: payoutData.destinationAccountId, // Bank account ID description: payoutData.description, externalId: payoutData.externalId // Your internal reference }, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } } ); return response.data; } // Usage const payout = await createPayoutOrder(token, orgId, merchantId, { currency: 'COP', amount: 500000, // 500,000 COP contactId: provider.id, destinationAccountId: bankAccount.id, description: 'Payment for Invoice #INV-001', externalId: `payout-inv-001-${Date.now()}` }); console.log('Payout created:', payout.id); console.log('Status:', payout.status); // "PROCESSING"
def create_payout_order(token, org_id, merchant_id, payout_data): import time response = requests.post( f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/orders', json={ 'type': 'PAYOUT', 'originCurrencySymbol': payout_data['currency'], 'destinationCurrencySymbol': payout_data['currency'], 'amountIn': payout_data['amount'], 'contactId': payout_data['contact_id'], 'destinationAccountId': payout_data['destination_account_id'], 'description': payout_data['description'], 'externalId': payout_data['external_id'] }, headers={ 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } ) response.raise_for_status() return response.json() # Usage payout = create_payout_order(token, org_id, merchant_id, { 'currency': 'COP', 'amount': 500000, 'contact_id': provider['id'], 'destination_account_id': bank_account['id'], 'description': 'Payment for Invoice #INV-001', 'external_id': f'payout-inv-001-{int(time.time())}' }) print(f"Payout created: {payout['id']}")
curl -X POST 'https://api-sandbox.koywe.com/api/v1/organizations/YOUR_ORG_ID/merchants/YOUR_MERCHANT_ID/orders' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "type": "PAYOUT", "originCurrencySymbol": "COP", "destinationCurrencySymbol": "COP", "amountIn": 500000, "contactId": "cnt_provider123", "destinationAccountId": "ba_xyz789", "description": "Payment for Invoice #INV-001", "externalId": "payout-inv-001-1699999999" }'

Response:

{ "id": "ord_payout123", "type": "PAYOUT", "status": "PROCESSING", "amountIn": 500000, "originCurrencySymbol": "COP", "destinationCurrencySymbol": "COP", "externalId": "payout-inv-001-1699999999", "description": "Payment for Invoice #INV-001", "createdAt": "2025-11-13T15:00:00Z" }

Step 5: Monitor Payout Status

Track the payout via webhooks or polling:

app.post('/webhooks/koywe', express.raw({type: 'application/json'}), async (req, res) => { // Convert raw body to string once const rawBody = req.body.toString(); // Verify signature with string body const signature = req.headers['koywe-signature']; if (!verifySignature(rawBody, signature, process.env.KOYWE_WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } // Parse JSON from string const event = JSON.parse(rawBody); switch (event.type) { case 'order.processing': console.log('Payout processing:', event.data.orderId); // Update internal status await updatePayoutStatus(event.data.externalId, 'processing'); break; case 'order.completed': console.log('Payout completed:', event.data.orderId); // Mark as paid in your system await markInvoiceAsPaid(event.data.externalId); await notifyProvider(event.data.externalId); break; case 'order.failed': console.log('Payout failed:', event.data.orderId); // Handle failure await handlePayoutFailure(event.data.externalId, event.data.errorMessage); break; } res.status(200).send('OK'); });

Via Polling (Alternative)

async function waitForPayoutCompletion(token, orgId, merchantId, orderId, maxAttempts = 20) { for (let i = 0; i < maxAttempts; i++) { const order = await axios.get( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders/${orderId}`, { headers: { 'Authorization': `Bearer ${token}` } } ); const status = order.data.status; console.log(`Attempt ${i + 1}: Status = ${status}`); if (status === 'COMPLETED') { console.log('✓ Payout completed successfully'); return order.data; } if (status === 'FAILED') { throw new Error(`Payout failed: ${order.data.errorMessage}`); } // Wait 5 seconds before checking again await new Promise(resolve => setTimeout(resolve, 5000)); } throw new Error('Timeout waiting for payout completion'); } // Usage const completed = await waitForPayoutCompletion(token, orgId, merchantId, payout.id); console.log('Payout completed at:', completed.dates.deliveryDate);

Complete End-to-End Example

async function payProvider(providerData, bankData, payoutData) { try { // 1. Authenticate console.log('1. Authenticating...'); const token = await authenticate(); // 2. Check balance console.log('\n2. Checking balance...'); const balance = await getBalance(token, orgId, merchantId, payoutData.currency); console.log(`Available: ${balance.availableBalance} ${payoutData.currency}`); if (balance.availableBalance < payoutData.amount) { throw new Error(`Insufficient balance. Need ${payoutData.amount}, have ${balance.availableBalance}`); } // 3. Create or get provider contact console.log('\n3. Creating provider contact...'); const provider = await createProviderContact(token, orgId, merchantId, providerData); console.log('✓ Provider contact:', provider.id); // 4. Add bank account console.log('\n4. Adding bank account...'); const bankAccount = await addProviderBankAccount(token, orgId, merchantId, provider.id, bankData); console.log('✓ Bank account:', bankAccount.id); // 5. Create payout console.log('\n5. Creating payout order...'); const payout = await createPayoutOrder(token, orgId, merchantId, { ...payoutData, contactId: provider.id, destinationAccountId: bankAccount.id }); console.log('✓ Payout created:', payout.id); console.log('✓ Status:', payout.status); // 6. Wait for completion (or use webhooks in production) console.log('\n6. Waiting for completion...'); const completed = await waitForPayoutCompletion(token, orgId, merchantId, payout.id); console.log('✓ Payout completed'); // 7. Update internal systems console.log('\n7. Updating internal records...'); await markInvoiceAsPaid(payoutData.externalId); console.log('\n✅ Success! Provider has been paid.'); return { success: true, payoutId: completed.id, amount: completed.amountIn, currency: completed.originCurrencySymbol }; } catch (error) { console.error('❌ Error:', error.message); throw error; } } // Usage await payProvider( { email: 'provider@example.com', fullName: 'Servicios ABC SAS', country: 'CO', documentType: 'NIT', documentNumber: '900123456-1', businessType: 'COMPANY' }, { country: 'CO', currency: 'COP', bankCode: 'BANCOLOMBIA', accountNumber: '1234567890', accountType: 'checking', holderName: 'Servicios ABC SAS' }, { currency: 'COP', amount: 500000, description: 'Payment for Invoice #INV-001', externalId: 'inv-001' } );

Best Practices

1. Always Check Balance

async function safeCreatePayout(token, orgId, merchantId, payoutData) { const balance = await getBalance(token, orgId, merchantId, payoutData.currency); if (balance.availableBalance < payoutData.amount) { throw new Error('INSUFFICIENT_BALANCE'); } return await createPayoutOrder(token, orgId, merchantId, payoutData); }

2. Verify Bank Account Details

  • Validate bank code exists for country
  • Verify account number format
  • Confirm account holder name matches contact
  • Test with small amount first

3. Use Idempotent External IDs

// Use invoice ID or unique reference const externalId = `payout-invoice-${invoiceId}`; // Safe to retry - same externalId returns same order const payout = await createPayoutOrder(token, orgId, merchantId, { ...payoutData, externalId: externalId });

4. Handle Failures Gracefully

try { const payout = await createPayoutOrder(token, orgId, merchantId, payoutData); } catch (error) { if (error.response?.data?.code === 'INSUFFICIENT_BALANCE') { // Handle insufficient balance await notifyAdminLowBalance(); } else if (error.response?.data?.code === 'INVALID_BANK_ACCOUNT') { // Handle invalid bank details await requestBankAccountUpdate(providerId); } else { // Log and retry later await queuePayoutForRetry(payoutData); } }

Next Steps

Last updated on