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
| 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:
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:
Via Webhooks (Recommended)
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