Paying Providers - Integration Guide
Complete step-by-step integration for payouts
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:
1 async function getBalance(token, orgId, merchantId, currencySymbol) { 2 const response = await axios.get( 3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/bank-accounts/balances`, 4 { 5 headers: { 'Authorization': `Bearer ${token}` } 6 } 7 ); 8 9 const balances = response.data; 10 const balance = balances.find(b => b.currencySymbol === currencySymbol); 11 12 return balance || { availableBalance: 0, currencySymbol }; 13 } 14 15 // Usage 16 const balance = await getBalance(token, orgId, merchantId, 'COP'); 17 console.log('Available balance:', balance.availableBalance, 'COP'); 18 19 // Check if sufficient 20 if (balance.availableBalance < 500000) { 21 console.error('Insufficient balance for payout'); 22 // Handle: add funds via PAYIN or show error 23 }
Critical: If balance is insufficient, the PAYOUT order will fail. Always check balance first.
Step 2: Create Provider Contact
Store provider information:
1 async function createProviderContact(token, orgId, merchantId, providerData) { 2 const response = await axios.post( 3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/contacts`, 4 { 5 email: providerData.email, 6 phone: providerData.phone, 7 fullName: providerData.fullName, 8 countrySymbol: providerData.country, 9 documentType: providerData.documentType, 10 documentNumber: providerData.documentNumber, 11 businessType: providerData.businessType // 'PERSON' or 'COMPANY' 12 }, 13 { 14 headers: { 15 'Authorization': `Bearer ${token}`, 16 'Content-Type': 'application/json' 17 } 18 } 19 ); 20 21 return response.data; 22 } 23 24 // Usage - Company provider 25 const provider = await createProviderContact(token, orgId, merchantId, { 26 email: 'provider@example.com', 27 phone: '+573001234567', 28 fullName: 'Servicios ABC SAS', 29 country: 'CO', 30 documentType: 'NIT', // Tax ID for companies in Colombia 31 documentNumber: '900123456-1', 32 businessType: 'COMPANY' 33 }); 34 35 console.log('Provider contact created:', provider.id);
Step 3: Add Provider Bank Account
Link the providerโs bank account details:
1 async function addProviderBankAccount(token, orgId, merchantId, contactId, bankData) { 2 const response = await axios.post( 3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/contacts/${contactId}/bank-accounts`, 4 { 5 countrySymbol: bankData.country, 6 currencySymbol: bankData.currency, 7 bankCode: bankData.bankCode, 8 accountNumber: bankData.accountNumber, 9 accountType: bankData.accountType, // 'checking' or 'savings' 10 holderName: bankData.holderName 11 }, 12 { 13 headers: { 14 'Authorization': `Bearer ${token}`, 15 'Content-Type': 'application/json' 16 } 17 } 18 ); 19 20 return response.data; 21 } 22 23 // Usage 24 const bankAccount = await addProviderBankAccount(token, orgId, merchantId, provider.id, { 25 country: 'CO', 26 currency: 'COP', 27 bankCode: 'BANCOLOMBIA', 28 accountNumber: '1234567890', 29 accountType: 'checking', 30 holderName: 'Servicios ABC SAS' 31 }); 32 33 console.log('Bank account added:', bankAccount.id);
Bank Codes by Country
| Country | Example Banks | Code Format |
|---|---|---|
| Colombia | BANCOLOMBIA, DAVIVIENDA, BOGOTA | Bank name |
| Brazil | BANCO_DO_BRASIL, BRADESCO, ITAU | Bank code |
| Mexico | BBVA_MEXICO, SANTANDER_MEXICO | Bank identifier |
| Chile | BANCO_CHILE, BCI, SANTANDER_CHILE | Bank name |
Complete bank codes reference โ
Step 4: Create PAYOUT Order
Create the payout order:
1 async function createPayoutOrder(token, orgId, merchantId, payoutData) { 2 const response = await axios.post( 3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`, 4 { 5 type: 'PAYOUT', 6 originCurrencySymbol: payoutData.currency, 7 destinationCurrencySymbol: payoutData.currency, // Same currency 8 amountIn: payoutData.amount, 9 contactId: payoutData.contactId, 10 destinationAccountId: payoutData.destinationAccountId, // Bank account ID 11 description: payoutData.description, 12 externalId: payoutData.externalId // Your internal reference 13 }, 14 { 15 headers: { 16 'Authorization': `Bearer ${token}`, 17 'Content-Type': 'application/json' 18 } 19 } 20 ); 21 22 return response.data; 23 } 24 25 // Usage 26 const payout = await createPayoutOrder(token, orgId, merchantId, { 27 currency: 'COP', 28 amount: 500000, // 500,000 COP 29 contactId: provider.id, 30 destinationAccountId: bankAccount.id, 31 description: 'Payment for Invoice #INV-001', 32 externalId: `payout-inv-001-${Date.now()}` 33 }); 34 35 console.log('Payout created:', payout.id); 36 console.log('Status:', payout.status); // "PROCESSING"
Response:
1 { 2 "id": "ord_payout123", 3 "type": "PAYOUT", 4 "status": "PROCESSING", 5 "amountIn": 500000, 6 "originCurrencySymbol": "COP", 7 "destinationCurrencySymbol": "COP", 8 "externalId": "payout-inv-001-1699999999", 9 "description": "Payment for Invoice #INV-001", 10 "createdAt": "2025-11-13T15:00:00Z" 11 }
Step 5: Monitor Payout Status
Track the payout via webhooks or polling:
Via Webhooks (Recommended)
Node.js Express
1 app.post('/webhooks/koywe', express.raw({type: 'application/json'}), async (req, res) => { 2 // Convert raw body to string once 3 const rawBody = req.body.toString(); 4 5 // Verify signature with string body 6 const signature = req.headers['koywe-signature']; 7 if (!verifySignature(rawBody, signature, process.env.KOYWE_WEBHOOK_SECRET)) { 8 return res.status(401).send('Invalid signature'); 9 } 10 11 // Parse JSON from string 12 const event = JSON.parse(rawBody); 13 14 switch (event.type) { 15 case 'order.processing': 16 console.log('Payout processing:', event.data.orderId); 17 // Update internal status 18 await updatePayoutStatus(event.data.externalId, 'processing'); 19 break; 20 21 case 'order.completed': 22 console.log('Payout completed:', event.data.orderId); 23 // Mark as paid in your system 24 await markInvoiceAsPaid(event.data.externalId); 25 await notifyProvider(event.data.externalId); 26 break; 27 28 case 'order.failed': 29 console.log('Payout failed:', event.data.orderId); 30 // Handle failure 31 await handlePayoutFailure(event.data.externalId, event.data.errorMessage); 32 break; 33 } 34 35 res.status(200).send('OK'); 36 });
Via Polling (Alternative)
Node.js
1 async function waitForPayoutCompletion(token, orgId, merchantId, orderId, maxAttempts = 20) { 2 for (let i = 0; i < maxAttempts; i++) { 3 const order = await axios.get( 4 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders/${orderId}`, 5 { headers: { 'Authorization': `Bearer ${token}` } } 6 ); 7 8 const status = order.data.status; 9 console.log(`Attempt ${i + 1}: Status = ${status}`); 10 11 if (status === 'COMPLETED') { 12 console.log('โ Payout completed successfully'); 13 return order.data; 14 } 15 16 if (status === 'FAILED') { 17 throw new Error(`Payout failed: ${order.data.errorMessage}`); 18 } 19 20 // Wait 5 seconds before checking again 21 await new Promise(resolve => setTimeout(resolve, 5000)); 22 } 23 24 throw new Error('Timeout waiting for payout completion'); 25 } 26 27 // Usage 28 const completed = await waitForPayoutCompletion(token, orgId, merchantId, payout.id); 29 console.log('Payout completed at:', completed.dates.deliveryDate);
Complete End-to-End Example
Node.js
1 async function payProvider(providerData, bankData, payoutData) { 2 try { 3 // 1. Authenticate 4 console.log('1. Authenticating...'); 5 const token = await authenticate(); 6 7 // 2. Check balance 8 console.log('\n2. Checking balance...'); 9 const balance = await getBalance(token, orgId, merchantId, payoutData.currency); 10 console.log(`Available: ${balance.availableBalance} ${payoutData.currency}`); 11 12 if (balance.availableBalance < payoutData.amount) { 13 throw new Error(`Insufficient balance. Need ${payoutData.amount}, have ${balance.availableBalance}`); 14 } 15 16 // 3. Create or get provider contact 17 console.log('\n3. Creating provider contact...'); 18 const provider = await createProviderContact(token, orgId, merchantId, providerData); 19 console.log('โ Provider contact:', provider.id); 20 21 // 4. Add bank account 22 console.log('\n4. Adding bank account...'); 23 const bankAccount = await addProviderBankAccount(token, orgId, merchantId, provider.id, bankData); 24 console.log('โ Bank account:', bankAccount.id); 25 26 // 5. Create payout 27 console.log('\n5. Creating payout order...'); 28 const payout = await createPayoutOrder(token, orgId, merchantId, { 29 ...payoutData, 30 contactId: provider.id, 31 destinationAccountId: bankAccount.id 32 }); 33 console.log('โ Payout created:', payout.id); 34 console.log('โ Status:', payout.status); 35 36 // 6. Wait for completion (or use webhooks in production) 37 console.log('\n6. Waiting for completion...'); 38 const completed = await waitForPayoutCompletion(token, orgId, merchantId, payout.id); 39 console.log('โ Payout completed'); 40 41 // 7. Update internal systems 42 console.log('\n7. Updating internal records...'); 43 await markInvoiceAsPaid(payoutData.externalId); 44 45 console.log('\nโ Success! Provider has been paid.'); 46 47 return { 48 success: true, 49 payoutId: completed.id, 50 amount: completed.amountIn, 51 currency: completed.originCurrencySymbol 52 }; 53 54 } catch (error) { 55 console.error('โ Error:', error.message); 56 throw error; 57 } 58 } 59 60 // Usage 61 await payProvider( 62 { 63 email: 'provider@example.com', 64 fullName: 'Servicios ABC SAS', 65 country: 'CO', 66 documentType: 'NIT', 67 documentNumber: '900123456-1', 68 businessType: 'COMPANY' 69 }, 70 { 71 country: 'CO', 72 currency: 'COP', 73 bankCode: 'BANCOLOMBIA', 74 accountNumber: '1234567890', 75 accountType: 'checking', 76 holderName: 'Servicios ABC SAS' 77 }, 78 { 79 currency: 'COP', 80 amount: 500000, 81 description: 'Payment for Invoice #INV-001', 82 externalId: 'inv-001' 83 } 84 );
Best Practices
1. Always Check Balance
Safe Payout Creation
1 async function safeCreatePayout(token, orgId, merchantId, payoutData) { 2 const balance = await getBalance(token, orgId, merchantId, payoutData.currency); 3 4 if (balance.availableBalance < payoutData.amount) { 5 throw new Error('INSUFFICIENT_BALANCE'); 6 } 7 8 return await createPayoutOrder(token, orgId, merchantId, payoutData); 9 }
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
Idempotency
1 // Use invoice ID or unique reference 2 const externalId = `payout-invoice-${invoiceId}`; 3 4 // Safe to retry - same externalId returns same order 5 const payout = await createPayoutOrder(token, orgId, merchantId, { 6 ...payoutData, 7 externalId: externalId 8 });
4. Handle Failures Gracefully
Error Handling
1 try { 2 const payout = await createPayoutOrder(token, orgId, merchantId, payoutData); 3 } catch (error) { 4 if (error.response?.data?.code === 'INSUFFICIENT_BALANCE') { 5 // Handle insufficient balance 6 await notifyAdminLowBalance(); 7 } else if (error.response?.data?.code === 'INVALID_BANK_ACCOUNT') { 8 // Handle invalid bank details 9 await requestBankAccountUpdate(providerId); 10 } else { 11 // Log and retry later 12 await queuePayoutForRetry(payoutData); 13 } 14 }