Skip to Content
Accepting PaymentsIntegration Guide

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


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" }'
const axios = require('axios'); async function authenticate() { const response = await axios.post( 'https://api-sandbox.koywe.com/api/v1/auth/sign-in', { apiKey: process.env.KOYWE_API_KEY, secret: process.env.KOYWE_SECRET } ); return response.data.token; } // Usage const token = await authenticate();
import requests import os def authenticate(): response = requests.post( 'https://api-sandbox.koywe.com/api/v1/auth/sign-in', json={ 'apiKey': os.environ['KOYWE_API_KEY'], 'secret': os.environ['KOYWE_SECRET'] } ) response.raise_for_status() return response.json()['token'] # Usage token = authenticate()

Response:

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

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

Production Token Management

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'
async function getPaymentMethods(token, countrySymbol, currencySymbol) { const response = await axios.get( 'https://api-sandbox.koywe.com/api/v1/payment-method', { params: { countrySymbol: countrySymbol, // e.g., 'CO' for Colombia currencySymbol: currencySymbol // e.g., 'COP' }, headers: { 'Authorization': `Bearer ${token}` } } ); return response.data; } // Usage - Get Colombian payment methods const methods = await getPaymentMethods(token, 'CO', 'COP'); console.log('Available methods:', methods); // Example response // [ // { // "method": "PSE", // "name": "PSE - Pagos Seguros en Línea", // "supportedCountries": ["CO"], // "supportedCurrencies": ["COP"], // "extra": { // "banks": ["BANCOLOMBIA", "DAVIVIENDA", "BOGOTA"] // } // }, // { // "method": "NEQUI", // "name": "Nequi", // "supportedCountries": ["CO"], // "supportedCurrencies": ["COP"] // } // ]
def get_payment_methods(token, country_symbol, currency_symbol): response = requests.get( 'https://api-sandbox.koywe.com/api/v1/payment-method', params={ 'countrySymbol': country_symbol, 'currencySymbol': currency_symbol }, headers={'Authorization': f'Bearer {token}'} ) response.raise_for_status() return response.json() # Usage methods = get_payment_methods(token, 'CO', 'COP') for method in methods: print(f"Method: {method['method']} - {method['name']}")

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`, { email: contactData.email, // Optional but recommended phone: contactData.phone, // Optional fullName: contactData.fullName, // Required countrySymbol: contactData.countrySymbol, // Required (e.g., 'CO') documentType: contactData.documentType, // Required (e.g., 'CC') documentNumber: contactData.documentNumber, // Required businessType: 'PERSON' // 'PERSON' or 'COMPANY' }, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } } ); return response.data; } // Usage const contact = await createContact(token, orgId, merchantId, { email: 'customer@example.com', phone: '+573001234567', fullName: 'Juan Pérez', countrySymbol: 'CO', documentType: 'CC', // Colombian ID documentNumber: '1234567890' }); console.log('Contact created:', contact.id);
def create_contact(token, org_id, merchant_id, contact_data): response = requests.post( f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/contacts', json={ 'email': contact_data.get('email'), 'phone': contact_data.get('phone'), 'fullName': contact_data['full_name'], 'countrySymbol': contact_data['country_symbol'], 'documentType': contact_data['document_type'], 'documentNumber': contact_data['document_number'], 'businessType': 'PERSON' }, headers={ 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } ) response.raise_for_status() return response.json() # Usage contact = create_contact(token, org_id, merchant_id, { 'email': 'customer@example.com', 'phone': '+573001234567', 'full_name': 'Juan Pérez', 'country_symbol': 'CO', 'document_type': 'CC', 'document_number': '1234567890' })
curl -X POST 'https://api-sandbox.koywe.com/api/v1/organizations/YOUR_ORG_ID/merchants/YOUR_MERCHANT_ID/contacts' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "email": "customer@example.com", "phone": "+573001234567", "fullName": "Juan Pérez", "countrySymbol": "CO", "documentType": "CC", "documentNumber": "1234567890", "businessType": "PERSON" }'

Document Types by Country

CountryCodeDocument TypeExample
ColombiaCCCédula de Ciudadanía1234567890
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.paymentUrl); // Redirect customer to order.paymentUrl
def create_payin_order(token, org_id, merchant_id, order_data): response = requests.post( f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/orders', json={ 'type': 'PAYIN', 'originCurrencySymbol': order_data['currency'], 'destinationCurrencySymbol': order_data['currency'], 'amountIn': order_data['amount'], 'description': order_data['description'], 'externalId': order_data['external_id'], 'contactId': order_data.get('contact_id'), 'paymentMethods': [ { 'method': order_data['payment_method'], 'extra': order_data.get('extra') } ], 'successUrl': order_data['success_url'], 'failedUrl': order_data['failed_url'] }, headers={ 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } ) response.raise_for_status() return response.json() # Usage import time order = create_payin_order(token, org_id, merchant_id, { 'currency': 'COP', 'amount': 50000, 'description': 'Payment for Order #12345', 'external_id': f'order-12345-{int(time.time())}', 'contact_id': contact['id'], 'payment_method': 'PSE', 'extra': 'BANCOLOMBIA', 'success_url': 'https://yoursite.com/payment/success', 'failed_url': 'https://yoursite.com/payment/failed' }) print(f"Order ID: {order['id']}") print(f"Payment URL: {order['paymentUrl']}")
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": "PAYIN", "originCurrencySymbol": "COP", "destinationCurrencySymbol": "COP", "amountIn": 50000, "description": "Payment for Order #12345", "externalId": "order-12345-1699999999", "contactId": "cnt_abc123", "paymentMethods": [ { "method": "PSE", "extra": { "bankAccount": { "name": "BANCOLOMBIA" } } } ], "successUrl": "https://yoursite.com/payment/success", "failedUrl": "https://yoursite.com/payment/failed" }'

Response:

{ "id": "ord_abc123xyz", "type": "PAYIN", "status": "PENDING", "amountIn": 50000, "originCurrencySymbol": "COP", "destinationCurrencySymbol": "COP", "paymentUrl": "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 paymentUrl 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.paymentUrl; // Or open in new window window.open(order.paymentUrl, '_blank'); // Or return URL to frontend res.json({ orderId: order.id, paymentUrl: order.paymentUrl });
function CheckoutButton() { const handleCheckout = async () => { try { // Call your backend to create order const response = await fetch('/api/create-payment', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: 50000, description: 'Order #12345' }) }); const { paymentUrl } = await response.json(); // Redirect to payment window.location.href = paymentUrl; } catch (error) { console.error('Payment error:', error); } }; return ( <button onClick={handleCheckout}> Pay Now </button> ); }

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); } });
from flask import Flask, request import hmac import hashlib import json app = Flask(__name__) @app.route('/webhooks/koywe', methods=['POST']) def webhook_handler(): # 1. Verify signature signature = request.headers.get('Koywe-Signature') secret = os.environ['KOYWE_WEBHOOK_SECRET'] expected_signature = hmac.new( secret.encode(), request.data, hashlib.sha256 ).hexdigest() if signature != expected_signature: return 'Invalid signature', 401 # 2. Parse event event = json.loads(request.data) event_id = event['id'] # 3. Check for duplicate if is_event_processed(event_id): return 'Already processed', 200 # 4. Handle event try: event_type = event['type'] order_data = event['data'] if event_type == 'order.paid': update_order_status(order_data['externalId'], 'paid') send_confirmation_email(order_data['externalId']) elif event_type == 'order.completed': update_order_status(order_data['externalId'], 'completed') # FULFILL THE ORDER HERE fulfill_order(order_data['externalId']) elif event_type == 'order.failed': update_order_status(order_data['externalId'], 'failed') send_failure_notification(order_data['externalId']) # Mark as processed mark_event_processed(event_id) return 'OK', 200 except Exception as error: print(f'Webhook error: {error}') log_webhook_error(event, error) return 'OK', 200 # Return 200 to prevent retries

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:

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:

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

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`, { email: customerData.email, phone: customerData.phone, fullName: customerData.fullName, countrySymbol: customerData.country, documentType: customerData.documentType, documentNumber: customerData.documentNumber, businessType: '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.paymentUrl); return { success: true, orderId: order.id, paymentUrl: order.paymentUrl, status: order.status }; } catch (error) { console.error('❌ Error:', error.response?.data || error.message); throw error; } } // Usage const result = await acceptPayment( { email: 'customer@example.com', phone: '+573001234567', fullName: 'Juan Pérez', country: 'CO', documentType: 'CC', 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.paymentUrl);

Error Handling

Common Errors and Solutions

Insufficient Balance

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

Invalid Payment Method

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

Invalid Document

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

Expired Token

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

Use sandbox URL

https://api-sandbox.koywe.com

Use test amount 666 for failures

Test failed payment scenario

Use any other amount for success

All other amounts succeed in sandbox

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

Last updated on