Skip to Content
Accepting PaymentsTroubleshooting

Troubleshooting Accepting Payments

Solutions to common issues when integrating payment acceptance.

Authentication Issues

401 Unauthorized Error

Problem: API returns 401 Unauthorized

Causes:

  • Invalid API key or secret
  • Expired token
  • Token not included in request

Solutions:

// Verify credentials are correct console.log('API Key:', process.env.KOYWE_API_KEY); console.log('Secret:', process.env.KOYWE_SECRET ? '***' : 'MISSING'); // Test authentication try { const token = await authenticate(); console.log('✓ Authentication successful'); } catch (error) { console.error('✗ Authentication failed:', error.response?.data); }
// Implement token caching with refresh class KoyweClient { constructor(apiKey, secret) { this.apiKey = apiKey; this.secret = secret; this.token = null; this.tokenExpiry = null; } async getToken() { // Return cached token if still valid (with 5-minute buffer) if (this.token && this.tokenExpiry > Date.now() + (5 * 60 * 1000)) { return this.token; } // Get new token const response = await axios.post( 'https://api-sandbox.koywe.com/api/v1/auth/sign-in', { apiKey: this.apiKey, secret: this.secret } ); this.token = response.data.token; this.tokenExpiry = Date.now() + (60 * 60 * 1000); // 1 hour return this.token; } async request(method, url, data) { const token = await this.getToken(); try { return await axios({ method, url, data, headers: { 'Authorization': `Bearer ${token}` } }); } catch (error) { // If 401, token might have expired, retry once if (error.response?.status === 401) { this.token = null; // Clear token const newToken = await this.getToken(); return await axios({ method, url, data, headers: { 'Authorization': `Bearer ${newToken}` } }); } throw error; } } }

Order Creation Issues

Payment Method Not Supported

Problem: Error “Payment method not supported for country/currency”

Cause: Using a payment method that isn’t available for the target country or currency

Solution:

async function createOrderSafely(country, currency, amount, preferredMethod) { // 1. Get available methods const methods = await getPaymentMethods(country, currency); console.log('Available methods:', methods.map(m => m.method)); // 2. Check if preferred method is available const methodAvailable = methods.some(m => m.method === preferredMethod); if (!methodAvailable) { console.error(`Method ${preferredMethod} not available`); console.log('Use one of:', methods.map(m => m.method)); throw new Error('Payment method not supported'); } // 3. Create order return await createPayinOrder(token, orgId, merchantId, { originCurrencySymbol: currency, destinationCurrencySymbol: currency, amountIn: amount, paymentMethods: [{ method: preferredMethod }] // ... other fields }); }

Common mistakes:

  • Using SPEI for Colombia (use PSE instead)
  • Using PSE for Brazil (use PIX instead)
  • Wrong currency for payment method

Invalid Document Number

Problem: Error “Invalid document number format”

Cause: Document number doesn’t match expected format for the document type

Solution:

function validateDocument(country, documentType, documentNumber) { const validators = { 'CO': { 'CC': /^\d{6,10}$/, // Colombian ID: 6-10 digits 'CE': /^\d{6,7}$/, // Foreign ID: 6-7 digits 'NIT': /^\d{9,10}$/ // Tax ID: 9-10 digits }, 'BR': { 'CPF': /^\d{11}$/, // Individual: 11 digits 'CNPJ': /^\d{14}$/ // Company: 14 digits }, 'MX': { 'RFC': /^[A-Z]{3,4}\d{6}[A-Z0-9]{3}$/ // Tax ID format }, 'CL': { 'RUT': /^\d{7,8}-[\dkK]$/ // Format: 12345678-9 } }; const regex = validators[country]?.[documentType]; if (!regex) { console.warn(`No validator for ${country} ${documentType}`); return true; // Allow if no validator } const isValid = regex.test(documentNumber); if (!isValid) { console.error(`Invalid ${documentType} format: ${documentNumber}`); console.log(`Expected format: ${regex}`); } return isValid; } // Usage const isValid = validateDocument('CO', 'CC', '1234567890'); if (!isValid) { throw new Error('Invalid document number'); }

Duplicate Order / Idempotency Issues

Problem: Creating duplicate orders or getting “Order already exists” error

Solution: Use externalId for idempotency

async function createOrderIdempotent(internalOrderId, orderData) { // Use your internal order ID as externalId const externalId = `order-${internalOrderId}`; try { const order = await createPayinOrder(token, orgId, merchantId, { ...orderData, externalId: externalId // Same externalId = same order }); console.log('Order created:', order.id); return order; } catch (error) { if (error.response?.status === 409) { // Order already exists, retrieve it console.log('Order already exists, retrieving...'); const existingOrder = await getOrderByExternalId(externalId); return existingOrder; } throw error; } } // Safe to retry const order = await createOrderIdempotent('12345', orderData);

Payment Flow Issues

Order Stuck in PENDING

Problem: Order stays in PENDING status and never completes

Causes in Production:

  • Customer hasn’t completed payment
  • Payment URL expired
  • Payment provider issues

Causes in Sandbox:

  • Using failure test amount (666)
  • Network issues

Solutions:

async function checkOrderStatus(orderId) { const order = await getOrderStatus(token, orgId, merchantId, orderId); console.log('Status:', order.status); console.log('Created:', order.createdAt); console.log('Due date:', order.dueDate); if (order.status === 'PENDING') { const createdTime = new Date(order.createdAt); const now = new Date(); const minutesElapsed = (now - createdTime) / 1000 / 60; console.log(`Order pending for ${minutesElapsed.toFixed(0)} minutes`); if (minutesElapsed > 30) { console.warn('Order pending for too long - customer may not have paid'); // Consider cancelling or expiring the order } } return order; }

In Sandbox:

  • Orders complete automatically after 5-30 seconds
  • If using amount 666, order will fail (this is intentional for testing)

Payment Completed but No Webhook Received

Problem: Order shows COMPLETED in API but webhook wasn’t received

Causes:

  • Webhook endpoint not configured
  • Webhook endpoint unreachable
  • Webhook endpoint returning errors
  • Firewall blocking webhooks

Solutions:

Verify webhook endpoint

Check that your endpoint is publicly accessible

Test with webhook.site

Use https://webhook.site  to see if webhooks are being sent

Check webhook logs

Query webhook delivery attempts:

const deliveries = await getWebhookDeliveries(orderId); console.log(deliveries);

Verify signature validation

Make sure you’re not rejecting webhooks due to failed signature verification

Implement fallback polling

As a backup, poll order status:

async function waitForCompletion(orderId, maxAttempts = 20) { for (let i = 0; i < maxAttempts; i++) { const order = await getOrderStatus(token, orgId, merchantId, orderId); if (order.status === 'COMPLETED') { return order; } if (['FAILED', 'EXPIRED', 'CANCELLED'].includes(order.status)) { throw new Error(`Order ${order.status}`); } // Wait 3 seconds before next check await new Promise(resolve => setTimeout(resolve, 3000)); } throw new Error('Timeout waiting for order completion'); }

Invalid Webhook Signature

Problem: Webhook signature verification fails

Cause: Incorrect signature calculation or wrong secret

Solution:

const crypto = require('crypto'); function verifyWebhookSignature(payload, signature, secret) { // payload should be the raw body (string or Buffer) // NOT parsed JSON const expectedSignature = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); const isValid = signature === expectedSignature; if (!isValid) { console.error('Signature mismatch'); console.error('Received:', signature); console.error('Expected:', expectedSignature); } return isValid; } // Express example - MUST use raw body app.post('/webhooks/koywe', express.raw({ type: 'application/json' }), // Important: raw, not json (req, res) => { const signature = req.headers['koywe-signature']; const secret = process.env.KOYWE_WEBHOOK_SECRET; // req.body is Buffer when using express.raw if (!verifyWebhookSignature(req.body, signature, secret)) { return res.status(401).send('Invalid signature'); } // Now parse const event = JSON.parse(req.body); // ... handle event res.status(200).send('OK'); } );

Amount and Currency Issues

Amount Validation Errors

Problem: “Invalid amount” or “Amount too low/high”

Causes:

  • Amount is 0 or negative
  • Amount exceeds limits
  • Decimal amounts where integers expected

Solutions:

function validateAmount(amount, currency) { // Minimum amounts per currency const minimums = { 'COP': 1000, // 1,000 COP 'BRL': 1, // 1 BRL 'MXN': 1, // 1 MXN 'CLP': 100, // 100 CLP 'USD': 0.01 // $0.01 USD }; // Maximum amounts per currency const maximums = { 'COP': 50000000, // 50M COP 'BRL': 100000, // 100K BRL 'MXN': 100000, // 100K MXN 'CLP': 10000000, // 10M CLP 'USD': 50000 // 50K USD }; const min = minimums[currency] || 1; const max = maximums[currency] || 1000000; if (amount < min) { throw new Error(`Amount too low. Minimum: ${min} ${currency}`); } if (amount > max) { throw new Error(`Amount too high. Maximum: ${max} ${currency}`); } // Check for invalid decimals (COP, CLP don't use decimals) if (['COP', 'CLP'].includes(currency) && amount % 1 !== 0) { throw new Error(`${currency} does not support decimal amounts`); } return true; } // Usage try { validateAmount(50000, 'COP'); // OK validateAmount(50.5, 'COP'); // Error: no decimals validateAmount(0, 'COP'); // Error: too low } catch (error) { console.error(error.message); }

Currency Mismatch

Problem: “Currency not supported” or mismatch errors

Cause: Using wrong currency for country or payment method

Solution:

const COUNTRY_CURRENCIES = { 'CO': ['COP'], // Colombia: only COP 'BR': ['BRL'], // Brazil: only BRL 'MX': ['MXN'], // Mexico: only MXN 'CL': ['CLP'], // Chile: only CLP 'AR': ['ARS'], // Argentina: only ARS 'PE': ['PEN'] // Peru: only PEN }; function validateCurrency(country, currency) { const validCurrencies = COUNTRY_CURRENCIES[country]; if (!validCurrencies) { throw new Error(`Country ${country} not supported`); } if (!validCurrencies.includes(currency)) { throw new Error( `Currency ${currency} not valid for ${country}. ` + `Use: ${validCurrencies.join(', ')}` ); } return true; } // Usage validateCurrency('CO', 'COP'); // OK validateCurrency('CO', 'USD'); // Error

Network and Timeout Issues

Request Timeout

Problem: API requests timeout

Causes:

  • Slow network
  • API under heavy load
  • Missing timeout configuration

Solutions:

const axios = require('axios'); // Create axios instance with proper timeouts const koyweApi = axios.create({ baseURL: 'https://api-sandbox.koywe.com/api/v1', timeout: 30000, // 30 seconds headers: { 'Content-Type': 'application/json' } }); // Add retry logic async function requestWithRetry(fn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { const isTimeout = error.code === 'ECONNABORTED'; const isNetworkError = error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED'; const isServerError = error.response?.status >= 500; const shouldRetry = (isTimeout || isNetworkError || isServerError) && i < maxRetries - 1; if (shouldRetry) { const delay = Math.pow(2, i) * 1000; // Exponential backoff console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); } else { throw error; } } } } // Usage const order = await requestWithRetry(() => createPayinOrder(token, orgId, merchantId, orderData) );

Contact Support

If you’ve tried these solutions and still have issues:

📧Email Support

soporte@koywe.com

Please include:

  • Order ID or External ID
  • Error message
  • Request/response examples (remove sensitive data)
  • Steps to reproduce
  • Environment (sandbox/production)

Diagnostic Checklist

Use this checklist when troubleshooting:

  • API credentials are correct
  • Using correct base URL (sandbox vs production)
  • Token is valid and not expired
  • Payment method is supported for country/currency
  • Amount meets minimum/maximum requirements
  • Document number format is correct
  • Currency matches country
  • Webhook endpoint is publicly accessible
  • Webhook signature verification is correct
  • Network connectivity is stable
  • Proper error handling is implemented
  • Timeout values are appropriate

Next Steps

Last updated on