Troubleshooting - Accepting Payments

Common issues and solutions

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:

1// Verify credentials are correct
2console.log('API Key:', process.env.KOYWE_API_KEY);
3console.log('Secret:', process.env.KOYWE_SECRET ? '***' : 'MISSING');
4
5// Test authentication
6try {
7 const token = await authenticate();
8 console.log('โœ“ Authentication successful');
9} catch (error) {
10 console.error('โœ— Authentication failed:', error.response?.data);
11}

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:

Query available methods first
1async function createOrderSafely(country, currency, amount, preferredMethod) {
2 // 1. Get available methods
3 const methods = await getPaymentMethods(country, currency);
4 console.log('Available methods:', methods.map(m => m.method));
5
6 // 2. Check if preferred method is available
7 const methodAvailable = methods.some(m => m.method === preferredMethod);
8
9 if (!methodAvailable) {
10 console.error(`Method ${preferredMethod} not available`);
11 console.log('Use one of:', methods.map(m => m.method));
12 throw new Error('Payment method not supported');
13 }
14
15 // 3. Create order
16 return await createPayinOrder(token, orgId, merchantId, {
17 originCurrencySymbol: currency,
18 destinationCurrencySymbol: currency,
19 amountIn: amount,
20 paymentMethods: [{ method: preferredMethod }]
21 // ... other fields
22 });
23}

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:

Validate document formats
1function validateDocument(country, documentType, documentNumber) {
2 const validators = {
3 'CO': {
4 'CC': /^\d{6,10}$/, // Colombian ID: 6-10 digits
5 'CE': /^\d{6,7}$/, // Foreign ID: 6-7 digits
6 'NIT': /^\d{9,10}$/ // Tax ID: 9-10 digits
7 },
8 'BR': {
9 'CPF': /^\d{11}$/, // Individual: 11 digits
10 'CNPJ': /^\d{14}$/ // Company: 14 digits
11 },
12 'MX': {
13 'RFC': /^[A-Z]{3,4}\d{6}[A-Z0-9]{3}$/ // Tax ID format
14 },
15 'CL': {
16 'RUT': /^\d{7,8}-[\dkK]$/ // Format: 12345678-9
17 }
18 };
19
20 const regex = validators[country]?.[documentType];
21 if (!regex) {
22 console.warn(`No validator for ${country} ${documentType}`);
23 return true; // Allow if no validator
24 }
25
26 const isValid = regex.test(documentNumber);
27 if (!isValid) {
28 console.error(`Invalid ${documentType} format: ${documentNumber}`);
29 console.log(`Expected format: ${regex}`);
30 }
31
32 return isValid;
33}
34
35// Usage
36const isValid = validateDocument('CO', 'CC', '1234567890');
37if (!isValid) {
38 throw new Error('Invalid document number');
39}

Duplicate Order / Idempotency Issues

Problem: Creating duplicate orders or getting โ€œOrder already existsโ€ error

Solution: Use externalId for idempotency

Proper idempotency
1async function createOrderIdempotent(internalOrderId, orderData) {
2 // Use your internal order ID as externalId
3 const externalId = `order-${internalOrderId}`;
4
5 try {
6 const order = await createPayinOrder(token, orgId, merchantId, {
7 ...orderData,
8 externalId: externalId // Same externalId = same order
9 });
10
11 console.log('Order created:', order.id);
12 return order;
13
14 } catch (error) {
15 if (error.response?.status === 409) {
16 // Order already exists, retrieve it
17 console.log('Order already exists, retrieving...');
18 const existingOrder = await getOrderByExternalId(externalId);
19 return existingOrder;
20 }
21 throw error;
22 }
23}
24
25// Safe to retry
26const 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:

Check order status
1async function checkOrderStatus(orderId) {
2 const order = await getOrderStatus(token, orgId, merchantId, orderId);
3
4 console.log('Status:', order.status);
5 console.log('Created:', order.createdAt);
6 console.log('Due date:', order.dueDate);
7
8 if (order.status === 'PENDING') {
9 const createdTime = new Date(order.createdAt);
10 const now = new Date();
11 const minutesElapsed = (now - createdTime) / 1000 / 60;
12
13 console.log(`Order pending for ${minutesElapsed.toFixed(0)} minutes`);
14
15 if (minutesElapsed > 30) {
16 console.warn('Order pending for too long - customer may not have paid');
17 // Consider cancelling or expiring the order
18 }
19 }
20
21 return order;
22}

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:

1

Verify webhook endpoint

Check that your endpoint is publicly accessible

2

Test with webhook.site

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

3

Check webhook logs

Query webhook delivery attempts:

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

Verify signature validation

Make sure youโ€™re not rejecting webhooks due to failed signature verification

5

Implement fallback polling

As a backup, poll order status:

1async function waitForCompletion(orderId, maxAttempts = 20) {
2 for (let i = 0; i < maxAttempts; i++) {
3 const order = await getOrderStatus(token, orgId, merchantId, orderId);
4
5 if (order.status === 'COMPLETED') {
6 return order;
7 }
8
9 if (['FAILED', 'EXPIRED', 'CANCELLED'].includes(order.status)) {
10 throw new Error(`Order ${order.status}`);
11 }
12
13 // Wait 3 seconds before next check
14 await new Promise(resolve => setTimeout(resolve, 3000));
15 }
16
17 throw new Error('Timeout waiting for order completion');
18}

Invalid Webhook Signature

Problem: Webhook signature verification fails

Cause: Incorrect signature calculation or wrong secret

Solution:

Correct signature verification
1const crypto = require('crypto');
2
3function verifyWebhookSignature(payload, signature, secret) {
4 // payload should be the raw body (string or Buffer)
5 // NOT parsed JSON
6
7 const expectedSignature = crypto
8 .createHmac('sha256', secret)
9 .update(payload)
10 .digest('hex');
11
12 const isValid = signature === expectedSignature;
13
14 if (!isValid) {
15 console.error('Signature mismatch');
16 console.error('Received:', signature);
17 console.error('Expected:', expectedSignature);
18 }
19
20 return isValid;
21}
22
23// Express example - MUST use raw body
24app.post('/webhooks/koywe',
25 express.raw({ type: 'application/json' }), // Important: raw, not json
26 (req, res) => {
27 const signature = req.headers['koywe-signature'];
28 const secret = process.env.KOYWE_WEBHOOK_SECRET;
29
30 // req.body is Buffer when using express.raw
31 if (!verifyWebhookSignature(req.body, signature, secret)) {
32 return res.status(401).send('Invalid signature');
33 }
34
35 // Now parse
36 const event = JSON.parse(req.body);
37 // ... handle event
38
39 res.status(200).send('OK');
40 }
41);

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:

Validate amounts
1function validateAmount(amount, currency) {
2 // Minimum amounts per currency
3 const minimums = {
4 'COP': 1000, // 1,000 COP
5 'BRL': 1, // 1 BRL
6 'MXN': 1, // 1 MXN
7 'CLP': 100, // 100 CLP
8 'USD': 0.01 // $0.01 USD
9 };
10
11 // Maximum amounts per currency
12 const maximums = {
13 'COP': 50000000, // 50M COP
14 'BRL': 100000, // 100K BRL
15 'MXN': 100000, // 100K MXN
16 'CLP': 10000000, // 10M CLP
17 'USD': 50000 // 50K USD
18 };
19
20 const min = minimums[currency] || 1;
21 const max = maximums[currency] || 1000000;
22
23 if (amount < min) {
24 throw new Error(`Amount too low. Minimum: ${min} ${currency}`);
25 }
26
27 if (amount > max) {
28 throw new Error(`Amount too high. Maximum: ${max} ${currency}`);
29 }
30
31 // Check for invalid decimals (COP, CLP don't use decimals)
32 if (['COP', 'CLP'].includes(currency) && amount % 1 !== 0) {
33 throw new Error(`${currency} does not support decimal amounts`);
34 }
35
36 return true;
37}
38
39// Usage
40try {
41 validateAmount(50000, 'COP'); // OK
42 validateAmount(50.5, 'COP'); // Error: no decimals
43 validateAmount(0, 'COP'); // Error: too low
44} catch (error) {
45 console.error(error.message);
46}

Currency Mismatch

Problem: โ€œCurrency not supportedโ€ or mismatch errors

Cause: Using wrong currency for country or payment method

Solution:

Country-currency mapping
1const COUNTRY_CURRENCIES = {
2 'CO': ['COP'], // Colombia: only COP
3 'BR': ['BRL'], // Brazil: only BRL
4 'MX': ['MXN'], // Mexico: only MXN
5 'CL': ['CLP'], // Chile: only CLP
6 'AR': ['ARS'], // Argentina: only ARS
7 'PE': ['PEN'] // Peru: only PEN
8};
9
10function validateCurrency(country, currency) {
11 const validCurrencies = COUNTRY_CURRENCIES[country];
12
13 if (!validCurrencies) {
14 throw new Error(`Country ${country} not supported`);
15 }
16
17 if (!validCurrencies.includes(currency)) {
18 throw new Error(
19 `Currency ${currency} not valid for ${country}. ` +
20 `Use: ${validCurrencies.join(', ')}`
21 );
22 }
23
24 return true;
25}
26
27// Usage
28validateCurrency('CO', 'COP'); // OK
29validateCurrency('CO', 'USD'); // Error

Network and Timeout Issues

Request Timeout

Problem: API requests timeout

Causes:

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

Solutions:

Configure timeouts
1const axios = require('axios');
2
3// Create axios instance with proper timeouts
4const koyweApi = axios.create({
5 baseURL: 'https://api-sandbox.koywe.com/api/v1',
6 timeout: 30000, // 30 seconds
7 headers: {
8 'Content-Type': 'application/json'
9 }
10});
11
12// Add retry logic
13async function requestWithRetry(fn, maxRetries = 3) {
14 for (let i = 0; i < maxRetries; i++) {
15 try {
16 return await fn();
17 } catch (error) {
18 const isTimeout = error.code === 'ECONNABORTED';
19 const isNetworkError = error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED';
20 const isServerError = error.response?.status >= 500;
21
22 const shouldRetry = (isTimeout || isNetworkError || isServerError) && i < maxRetries - 1;
23
24 if (shouldRetry) {
25 const delay = Math.pow(2, i) * 1000; // Exponential backoff
26 console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms...`);
27 await new Promise(resolve => setTimeout(resolve, delay));
28 } else {
29 throw error;
30 }
31 }
32 }
33}
34
35// Usage
36const order = await requestWithRetry(() =>
37 createPayinOrder(token, orgId, merchantId, orderData)
38);

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