Koywe
On this page

Accepting Payments Integration Guide

This comprehensive guide walks you through integrating payment acceptance into your application for production use.

Prerequisites

Before you begin:

  • API credentials (key, secret, organizationId, merchantId)
  • Webhook endpoint configured (recommended)
  • Test environment access
  • Understanding of Core Concepts
  • Familiarity with Order Types

Integration Overview

Drawing diagram…

Step 1: Authenticate

Obtain an access token for all subsequent requests:

curl -X POST 'https://api-sandbox.koywe.com/api/v1/auth/sign-in' \
  -H 'Content-Type: application/json' \
  -d '{
    "apiKey": "your_api_key",
    "secret": "your_secret"
  }'

Response:

JSON
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Token Management: Tokens expire after 1 hour. Implement token caching and refresh logic in production.

Production Token Management

JavaScript
class KoyweClient {
  constructor(apiKey, secret, baseUrl) {
    this.apiKey = apiKey;
    this.secret = secret;
    this.baseUrl = baseUrl;
    this.token = null;
    this.tokenExpiry = null;
  }
  
  async getToken() {
    // Return cached token if still valid
    if (this.token && this.tokenExpiry > Date.now()) {
      return this.token;
    }
    
    // Get new token
    const response = await axios.post(`${this.baseUrl}/auth/sign-in`, {
      apiKey: this.apiKey,
      secret: this.secret
    });
    
    this.token = response.data.token;
    // Set expiry to 55 minutes (5 min buffer)
    this.tokenExpiry = Date.now() + (55 * 60 * 1000);
    
    return this.token;
  }
}

Step 2: Get Available Payment Methods

Query which payment methods are available for your target country and currency:

curl -X GET 'https://api-sandbox.koywe.com/api/v1/payment-method?countrySymbol=CO&currencySymbol=COP' \
  -H 'Authorization: Bearer YOUR_TOKEN'

Caching: Cache payment methods per country/currency to reduce API calls. Methods don’t change frequently.


Create a contact for the customer to track payment history and meet compliance requirements:

async function createContact(token, orgId, merchantId, contactData) {
  const response = await axios.post(
    `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/contacts`,
    {
      firstName: contactData.firstName,            // Required
      lastName: contactData.lastName,              // Optional
      email: contactData.email,                    // Optional but recommended
      phone: contactData.phone,                    // Optional
      countrySymbol: contactData.countrySymbol,    // Required (e.g., 'CO')
      documentType: contactData.documentType,      // Optional (e.g., 'CED_CIU')
      documentNumber: contactData.documentNumber,  // Optional (required if documentType is provided)
      businessType: 'PERSON',                      // 'PERSON' or 'COMPANY'
      type: 'PERSON'                               // Required: PERSON, BUSINESS, GOVERNMENT, NGO, FOREIGN
    },
    {
      headers: { 
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    }
  );
  
  return response.data;
}
 
// Usage
const contact = await createContact(token, orgId, merchantId, {
  firstName: 'Juan',
  lastName: 'Pérez',
  email: 'customer@example.com',
  phone: '+573001234567',
  countrySymbol: 'CO',
  documentType: 'CED_CIU',  // Cédula de Ciudadanía (optional)
  documentNumber: '1234567890'
});
 
console.log('Contact created:', contact.id);

Document Types by Country

CountryCodeDocument TypeExample
ColombiaCED_CIUCédula de Ciudadanía (CC)1234567890
BrazilCPFCadastro de Pessoas Físicas12345678900
MexicoRFCRegistro Federal de ContribuyentesXAXX010101000
ChileRUTRol Único Tributario11111111-1

Complete document types reference →


Step 4: Create PAYIN Order

Create the payment order with all required details:

async function createPayinOrder(token, orgId, merchantId, orderData) {
  const response = await axios.post(
    `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`,
    {
      type: 'PAYIN',                                // Order type
      originCurrencySymbol: orderData.currency,     // e.g., 'COP'
      destinationCurrencySymbol: orderData.currency, // Same as origin for PAYIN
      amountIn: orderData.amount,                   // Amount to collect
      description: orderData.description,           // Customer-facing description
      externalId: orderData.externalId,             // Your internal reference (for idempotency)
      contactId: orderData.contactId,               // Contact from Step 3 (optional)
      paymentMethods: [                             // At least one payment method required
        {
          method: orderData.paymentMethod,          // e.g., 'PSE', 'PIX', 'SPEI'
          extra: orderData.extra                    // Additional data (e.g., bank for PSE)
        }
      ],
      successUrl: orderData.successUrl,             // Redirect after success
      failedUrl: orderData.failedUrl                // Redirect after failure
    },
    {
      headers: { 
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    }
  );
  
  return response.data;
}
 
// Usage
const order = await createPayinOrder(token, orgId, merchantId, {
  currency: 'COP',
  amount: 50000,                        // 50,000 COP
  description: 'Payment for Order #12345',
  externalId: `order-12345-${Date.now()}`,  // Unique per attempt
  contactId: contact.id,
  paymentMethod: 'PSE',
  extra: { bankAccount: { name: 'BANCOLOMBIA' } },  // Bank selection for PSE (per-method extras)
  successUrl: 'https://yoursite.com/payment/success',
  failedUrl: 'https://yoursite.com/payment/failed'
});
 
console.log('Order ID:', order.id);
console.log('Payment URL:', order.providedAction);
// Redirect customer to order.providedAction

Response:

JSON
{
  "id": "ord_abc123xyz",
  "type": "PAYIN",
  "status": "PENDING",
  "amountIn": 50000,
  "originCurrencySymbol": "COP",
  "destinationCurrencySymbol": "COP",
  "providedAction": "https://checkout.koywe.com/pay/ord_abc123xyz",
  "externalId": "order-12345-1699999999",
  "description": "Payment for Order #12345",
  "createdAt": "2025-11-13T10:00:00Z"
}

Payment URL Generated: The providedAction is where you should redirect your customer to complete the payment.


Step 5: Redirect Customer to Payment

Send the customer to the payment URL:

// Direct redirect
window.location.href = order.providedAction;
 
// Or open in new window
window.open(order.providedAction, '_blank');
 
// Or return URL to frontend
res.json({ 
  orderId: order.id,
  providedAction: order.providedAction 
});

Step 6: Handle Webhooks

Listen for webhook events to track payment status:

const express = require('express');
const crypto = require('crypto');
 
app.post('/webhooks/koywe', express.raw({type: 'application/json'}), async (req, res) => {
  // Convert raw body to string once
  const rawBody = req.body.toString();
  
  // 1. Verify webhook signature
  const signature = req.headers['koywe-signature'];
  const secret = process.env.KOYWE_WEBHOOK_SECRET;
  
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  
  if (signature !== expectedSignature) {
    console.error('Invalid webhook signature');
    return res.status(401).send('Invalid signature');
  }
  
  // 2. Parse event from string
  const event = JSON.parse(rawBody);
  const eventId = event.id;
  
  // 3. Check for duplicate (idempotency)
  if (await isEventProcessed(eventId)) {
    return res.status(200).send('Already processed');
  }
  
  // 4. Handle event based on type
  try {
    switch (event.type) {
      case 'order.created':
        console.log('Order created:', event.data.orderId);
        break;
        
      case 'order.pending':
        console.log('Order pending:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'pending');
        break;
        
      case 'order.processing':
        console.log('Payment processing:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'processing');
        break;
        
      case 'order.paid':
        console.log('Payment confirmed:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'paid');
        await sendConfirmationEmail(event.data.externalId);
        break;
        
      case 'order.completed':
        console.log('Funds credited:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'completed');
        // FULFILL THE ORDER HERE
        await fulfillOrder(event.data.externalId);
        await sendShippingNotification(event.data.externalId);
        break;
        
      case 'order.failed':
        console.log('Payment failed:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'failed');
        await sendFailureNotification(event.data.externalId);
        break;
        
      case 'order.expired':
        console.log('Order expired:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'expired');
        break;
        
      case 'order.cancelled':
        console.log('Order cancelled:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'cancelled');
        break;
    }
    
    // 5. Mark event as processed
    await markEventProcessed(eventId);
    
    // 6. Respond quickly (< 5 seconds)
    res.status(200).send('OK');
    
  } catch (error) {
    console.error('Webhook processing error:', error);
    // Still return 200 to prevent retries for processing errors
    res.status(200).send('OK');
    // Log error for manual review
    await logWebhookError(event, error);
  }
});

Critical: Always verify webhook signatures to ensure the webhook is from Koywe.

Complete webhooks guide →


Step 7: Check Order Status (Alternative/Supplement to Webhooks)

Query order status directly if needed:

JavaScript
async function getOrderStatus(token, orgId, merchantId, orderId) {
  const response = await axios.get(
    `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders/${orderId}`,
    {
      headers: { 
        'Authorization': `Bearer ${token}`
      }
    }
  );
  
  return response.data;
}
 
// Usage
const order = await getOrderStatus(token, orgId, merchantId, 'ord_abc123');
console.log('Current status:', order.status);
console.log('Amount:', order.amountIn, order.originCurrencySymbol);

Response:

JSON
{
  "id": "ord_abc123xyz",
  "type": "PAYIN",
  "status": "COMPLETED",
  "amountIn": 50000,
  "originCurrencySymbol": "COP",
  "destinationCurrencySymbol": "COP",
  "externalId": "order-12345",
  "dates": {
    "confirmationDate": "2025-11-13T10:00:00Z",
    "paymentDate": "2025-11-13T10:05:00Z",
    "deliveryDate": "2025-11-13T10:06:00Z"
  }
}

Complete Integration Example

Here’s a full end-to-end example:

JavaScript
const axios = require('axios');
 
const BASE_URL = 'https://api-sandbox.koywe.com/api/v1';
const ORG_ID = process.env.KOYWE_ORG_ID;
const MERCHANT_ID = process.env.KOYWE_MERCHANT_ID;
const API_KEY = process.env.KOYWE_API_KEY;
const SECRET = process.env.KOYWE_SECRET;
 
async function acceptPayment(customerData, orderDetails) {
  try {
    // 1. Authenticate
    console.log('1. Authenticating...');
    const authResponse = await axios.post(`${BASE_URL}/auth/sign-in`, {
      apiKey: API_KEY,
      secret: SECRET
    });
    const token = authResponse.data.token;
    console.log('✓ Authenticated');
 
    const headers = { 
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    };
 
    // 2. Get payment methods
    console.log('\n2. Getting payment methods...');
    const methodsResponse = await axios.get(
      `${BASE_URL}/payment-method`,
      {
        params: { 
          countrySymbol: customerData.country,
          currencySymbol: orderDetails.currency
        },
        headers: { 'Authorization': `Bearer ${token}` }
      }
    );
    const methods = methodsResponse.data;
    console.log('✓ Available methods:', methods.map(m => m.method).join(', '));
 
    // 3. Create contact
    console.log('\n3. Creating customer contact...');
    const contactResponse = await axios.post(
      `${BASE_URL}/organizations/${ORG_ID}/merchants/${MERCHANT_ID}/contacts`,
      {
        firstName: customerData.firstName,
        lastName: customerData.lastName,
        email: customerData.email,
        phone: customerData.phone,
        countrySymbol: customerData.country,
        documentType: customerData.documentType,
        documentNumber: customerData.documentNumber,
        businessType: 'PERSON',
        type: 'PERSON'
      },
      { headers }
    );
    const contactId = contactResponse.data.id;
    console.log('✓ Contact created:', contactId);
 
    // 4. Create PAYIN order
    console.log('\n4. Creating payment order...');
    const orderResponse = await axios.post(
      `${BASE_URL}/organizations/${ORG_ID}/merchants/${MERCHANT_ID}/orders`,
      {
        type: 'PAYIN',
        originCurrencySymbol: orderDetails.currency,
        destinationCurrencySymbol: orderDetails.currency,
        amountIn: orderDetails.amount,
        description: orderDetails.description,
        externalId: orderDetails.orderId,
        contactId: contactId,
        paymentMethods: [
          {
            method: orderDetails.paymentMethod,
            extra: orderDetails.bank
          }
        ],
        successUrl: 'https://yoursite.com/payment/success',
        failedUrl: 'https://yoursite.com/payment/failed'
      },
      { headers }
    );
    const order = orderResponse.data;
    console.log('✓ Order created:', order.id);
    console.log('✓ Status:', order.status);
 
    // 5. Return payment URL
    console.log('\n5. Payment URL ready:');
    console.log(order.providedAction);
    
    return {
      success: true,
      orderId: order.id,
      providedAction: order.providedAction,
      status: order.status
    };
 
  } catch (error) {
    console.error('❌ Error:', error.response?.data || error.message);
    throw error;
  }
}
 
// Usage
const result = await acceptPayment(
  {
    firstName: 'Juan',
    lastName: 'Pérez',
    email: 'customer@example.com',
    phone: '+573001234567',
    country: 'CO',
    documentType: 'CED_CIU',
    documentNumber: '1234567890'
  },
  {
    currency: 'COP',
    amount: 50000,
    description: 'Payment for Order #12345',
    orderId: `order-${Date.now()}`,
    paymentMethod: 'PSE',
    bank: 'BANCOLOMBIA'
  }
);
 
console.log('\n✅ Complete! Redirect customer to:');
console.log(result.providedAction);

Error Handling

Common Errors and Solutions

Insufficient Balance

JavaScript
// Error: Insufficient balance
// Solution: This shouldn't happen for PAYIN (only for PAYOUT)
// Check that you're using correct order type

Invalid Payment Method

JavaScript
// Error: Payment method not supported
// Solution: Get available methods first (Step 2)
const methods = await getPaymentMethods(country, currency);
// Use only returned methods

Invalid Document

JavaScript
// Error: Invalid document number
// Solution: Validate document format per country
// Colombia CC: 6-10 digits
// Brazil CPF: 11 digits
// etc.

Expired Token

JavaScript
// Error: 401 Unauthorized
// Solution: Implement token refresh
if (error.response?.status === 401) {
  token = await authenticate();
  // Retry request
}

Complete error handling guide →


Testing

Test your integration in sandbox:

  1. Use sandbox URL

    https://api-sandbox.koywe.com

  2. Use test amount 666 for failures

    Test failed payment scenario

  3. Use any other amount for success

    All other amounts succeed in sandbox

  4. Verify webhook handling

    Use webhook.site to inspect webhooks

Complete testing guide →


Production Checklist

Before going live:

  • Change to production URL (https://api.koywe.com)
  • Update to production API credentials
  • Implement webhook signature verification
  • Setup error logging and monitoring
  • Test with small real amounts
  • Implement token caching and refresh
  • Setup retry logic for transient failures
  • Configure proper timeout values
  • Implement idempotency with externalId
  • Test all payment methods you’ll support

Next Steps