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:

1async 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
16const balance = await getBalance(token, orgId, merchantId, 'COP');
17console.log('Available balance:', balance.availableBalance, 'COP');
18
19// Check if sufficient
20if (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:

1async 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
25const 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
35console.log('Provider contact created:', provider.id);

Step 3: Add Provider Bank Account

Link the providerโ€™s bank account details:

1async 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
24const 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
33console.log('Bank account added:', bankAccount.id);

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:

1async 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
26const 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
35console.log('Payout created:', payout.id);
36console.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:

Node.js Express
1app.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
1async 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
28const completed = await waitForPayoutCompletion(token, orgId, merchantId, payout.id);
29console.log('Payout completed at:', completed.dates.deliveryDate);

Complete End-to-End Example

Node.js
1async 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
61await 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
1async 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
2const externalId = `payout-invoice-${invoiceId}`;
3
4// Safe to retry - same externalId returns same order
5const payout = await createPayoutOrder(token, orgId, merchantId, {
6 ...payoutData,
7 externalId: externalId
8});

4. Handle Failures Gracefully

Error Handling
1try {
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}

Next Steps