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¤cySymbol=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.
Step 3: Create Contact (Optional but Recommended)
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
| Country | Code | Document Type | Example |
|---|---|---|---|
| Colombia | CC | Cédula de Ciudadanía | 1234567890 |
| Brazil | CPF | Cadastro de Pessoas Físicas | 12345678900 |
| Mexico | RFC | Registro Federal de Contribuyentes | XAXX010101000 |
| Chile | RUT | Rol Único Tributario | 11111111-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.paymentUrldef 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 retriesCritical: Always verify webhook signatures to ensure the webhook is from Koywe.
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 typeInvalid Payment Method
// Error: Payment method not supported
// Solution: Get available methods first (Step 2)
const methods = await getPaymentMethods(country, currency);
// Use only returned methodsInvalid 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
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