Koywe
On this page

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
}

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`,
    {
      firstName: providerData.firstName,
      countrySymbol: providerData.country,
      documentType: providerData.documentType,
      documentNumber: providerData.documentNumber,
      businessType: providerData.businessType,  // 'PERSON' or 'COMPANY'
      type: providerData.type,                  // 'PERSON', 'BUSINESS', etc.
      email: providerData.email,
      phone: providerData.phone
    },
    {
      headers: { 
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    }
  );
  
  return response.data;
}
 
// Usage - Company provider
const provider = await createProviderContact(token, orgId, merchantId, {
  firstName: 'Servicios ABC SAS',
  country: 'CO',
  documentType: 'NIT',  // Tax ID for companies in Colombia
  documentNumber: '9001234561',
  businessType: 'COMPANY',
  type: 'BUSINESS',
  email: 'provider@example.com',
  phone: '+573001234567'
});
 
console.log('Provider contact created:', provider.id);

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`,
    {
      name: bankData.name,                // Account display name
      kind: 'BANK',                       // 'BANK' or 'CRYPTO'
      isDefault: bankData.isDefault,
      countrySymbol: bankData.country,
      currencySymbol: bankData.currency,
      entity: bankData.entity,            // Bank code (e.g., 'BANCOLOMBIA')
      accountNumber: bankData.accountNumber,
      type: bankData.type                 // 'CHECKING', 'SAVINGS', or 'VIRTUAL'
    },
    {
      headers: { 
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    }
  );
  
  return response.data;
}
 
// Usage
const bankAccount = await addProviderBankAccount(token, orgId, merchantId, provider.id, {
  name: 'Bancolombia COP',
  country: 'CO',
  currency: 'COP',
  entity: 'BANCOLOMBIA',
  accountNumber: '1234567890',
  type: 'CHECKING',
  isDefault: true
});
 
console.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:

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"

Response:

JSON
{
  "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:

JavaScript
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)

JavaScript
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

JavaScript
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(
  {
    firstName: 'Servicios ABC SAS',
    countrySymbol: 'CO',
    documentType: 'NIT',
    documentNumber: '9001234561',
    businessType: 'COMPANY',
    type: 'BUSINESS',
    email: 'provider@example.com'
  },
  {
    name: 'Bancolombia COP',
    kind: 'BANK',
    isDefault: true,
    countrySymbol: 'CO',
    currencySymbol: 'COP',
    entity: 'BANCOLOMBIA',
    accountNumber: '1234567890',
    type: 'CHECKING'
  },
  {
    currency: 'COP',
    amount: 500000,
    description: 'Payment for Invoice #INV-001',
    externalId: 'inv-001'
  }
);

Best Practices

1. Always Check Balance

JavaScript
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

JavaScript
// 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

JavaScript
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