Accepting Payments - Integration Guide

Complete step-by-step integration for production

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"
> }'

Response:

1{
2 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
3}

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

Production Token Management

Node.js
1class KoyweClient {
2 constructor(apiKey, secret, baseUrl) {
3 this.apiKey = apiKey;
4 this.secret = secret;
5 this.baseUrl = baseUrl;
6 this.token = null;
7 this.tokenExpiry = null;
8 }
9
10 async getToken() {
11 // Return cached token if still valid
12 if (this.token && this.tokenExpiry > Date.now()) {
13 return this.token;
14 }
15
16 // Get new token
17 const response = await axios.post(`${this.baseUrl}/auth/sign-in`, {
18 apiKey: this.apiKey,
19 secret: this.secret
20 });
21
22 this.token = response.data.token;
23 // Set expiry to 55 minutes (5 min buffer)
24 this.tokenExpiry = Date.now() + (55 * 60 * 1000);
25
26 return this.token;
27 }
28}

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-methods?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:

1async function createContact(token, orgId, merchantId, contactData) {
2 const response = await axios.post(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/contacts`,
4 {
5 email: contactData.email, // Optional but recommended
6 phone: contactData.phone, // Optional
7 fullName: contactData.fullName, // Required
8 countrySymbol: contactData.countrySymbol, // Required (e.g., 'CO')
9 documentType: contactData.documentType, // Required (e.g., 'CC')
10 documentNumber: contactData.documentNumber, // Required
11 businessType: 'PERSON' // 'PERSON' or 'COMPANY'
12 },
13 {
14 headers: {
15 'Authorization': `Bearer ${token}`,
16 'Content-Type': 'application/json'
17 }
18 }
19 );
20
21 return response.data;
22}
23
24// Usage
25const contact = await createContact(token, orgId, merchantId, {
26 email: 'customer@example.com',
27 phone: '+573001234567',
28 fullName: 'Juan Pérez',
29 countrySymbol: 'CO',
30 documentType: 'CC', // Colombian ID
31 documentNumber: '1234567890'
32});
33
34console.log('Contact created:', contact.id);

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:

1async function createPayinOrder(token, orgId, merchantId, orderData) {
2 const response = await axios.post(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`,
4 {
5 type: 'PAYIN', // Order type
6 originCurrencySymbol: orderData.currency, // e.g., 'COP'
7 destinationCurrencySymbol: orderData.currency, // Same as origin for PAYIN
8 amountIn: orderData.amount, // Amount to collect
9 description: orderData.description, // Customer-facing description
10 externalId: orderData.externalId, // Your internal reference (for idempotency)
11 contactId: orderData.contactId, // Contact from Step 3 (optional)
12 paymentMethods: [ // At least one payment method required
13 {
14 method: orderData.paymentMethod, // e.g., 'PSE', 'PIX', 'SPEI'
15 extra: orderData.extra // Additional data (e.g., bank for PSE)
16 }
17 ],
18 successUrl: orderData.successUrl, // Redirect after success
19 failedUrl: orderData.failedUrl // Redirect after failure
20 },
21 {
22 headers: {
23 'Authorization': `Bearer ${token}`,
24 'Content-Type': 'application/json'
25 }
26 }
27 );
28
29 return response.data;
30}
31
32// Usage
33const order = await createPayinOrder(token, orgId, merchantId, {
34 currency: 'COP',
35 amount: 50000, // 50,000 COP
36 description: 'Payment for Order #12345',
37 externalId: `order-12345-${Date.now()}`, // Unique per attempt
38 contactId: contact.id,
39 paymentMethod: 'PSE',
40 extra: 'BANCOLOMBIA', // Bank selection for PSE
41 successUrl: 'https://yoursite.com/payment/success',
42 failedUrl: 'https://yoursite.com/payment/failed'
43});
44
45console.log('Order ID:', order.id);
46console.log('Payment URL:', order.paymentUrl);
47// Redirect customer to order.paymentUrl

Response:

1{
2 "id": "ord_abc123xyz",
3 "type": "PAYIN",
4 "status": "PENDING",
5 "amountIn": 50000,
6 "originCurrencySymbol": "COP",
7 "destinationCurrencySymbol": "COP",
8 "paymentUrl": "https://checkout.koywe.com/pay/ord_abc123xyz",
9 "externalId": "order-12345-1699999999",
10 "description": "Payment for Order #12345",
11 "createdAt": "2025-11-13T10:00:00Z"
12}

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:

1// Direct redirect
2window.location.href = order.paymentUrl;
3
4// Or open in new window
5window.open(order.paymentUrl, '_blank');
6
7// Or return URL to frontend
8res.json({
9 orderId: order.id,
10 paymentUrl: order.paymentUrl
11});

Step 6: Handle Webhooks

Listen for webhook events to track payment status:

1const express = require('express');
2const crypto = require('crypto');
3
4app.post('/webhooks/koywe', express.raw({type: 'application/json'}), async (req, res) => {
5 // Convert raw body to string once
6 const rawBody = req.body.toString();
7
8 // 1. Verify webhook signature
9 const signature = req.headers['koywe-signature'];
10 const secret = process.env.KOYWE_WEBHOOK_SECRET;
11
12 const expectedSignature = crypto
13 .createHmac('sha256', secret)
14 .update(rawBody)
15 .digest('hex');
16
17 if (signature !== expectedSignature) {
18 console.error('Invalid webhook signature');
19 return res.status(401).send('Invalid signature');
20 }
21
22 // 2. Parse event from string
23 const event = JSON.parse(rawBody);
24 const eventId = event.id;
25
26 // 3. Check for duplicate (idempotency)
27 if (await isEventProcessed(eventId)) {
28 return res.status(200).send('Already processed');
29 }
30
31 // 4. Handle event based on type
32 try {
33 switch (event.type) {
34 case 'order.created':
35 console.log('Order created:', event.data.orderId);
36 break;
37
38 case 'order.pending':
39 console.log('Order pending:', event.data.orderId);
40 await updateOrderStatus(event.data.externalId, 'pending');
41 break;
42
43 case 'order.processing':
44 console.log('Payment processing:', event.data.orderId);
45 await updateOrderStatus(event.data.externalId, 'processing');
46 break;
47
48 case 'order.paid':
49 console.log('Payment confirmed:', event.data.orderId);
50 await updateOrderStatus(event.data.externalId, 'paid');
51 await sendConfirmationEmail(event.data.externalId);
52 break;
53
54 case 'order.completed':
55 console.log('Funds credited:', event.data.orderId);
56 await updateOrderStatus(event.data.externalId, 'completed');
57 // FULFILL THE ORDER HERE
58 await fulfillOrder(event.data.externalId);
59 await sendShippingNotification(event.data.externalId);
60 break;
61
62 case 'order.failed':
63 console.log('Payment failed:', event.data.orderId);
64 await updateOrderStatus(event.data.externalId, 'failed');
65 await sendFailureNotification(event.data.externalId);
66 break;
67
68 case 'order.expired':
69 console.log('Order expired:', event.data.orderId);
70 await updateOrderStatus(event.data.externalId, 'expired');
71 break;
72
73 case 'order.cancelled':
74 console.log('Order cancelled:', event.data.orderId);
75 await updateOrderStatus(event.data.externalId, 'cancelled');
76 break;
77 }
78
79 // 5. Mark event as processed
80 await markEventProcessed(eventId);
81
82 // 6. Respond quickly (< 5 seconds)
83 res.status(200).send('OK');
84
85 } catch (error) {
86 console.error('Webhook processing error:', error);
87 // Still return 200 to prevent retries for processing errors
88 res.status(200).send('OK');
89 // Log error for manual review
90 await logWebhookError(event, error);
91 }
92});

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:

Node.js
1async function getOrderStatus(token, orgId, merchantId, orderId) {
2 const response = await axios.get(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders/${orderId}`,
4 {
5 headers: {
6 'Authorization': `Bearer ${token}`
7 }
8 }
9 );
10
11 return response.data;
12}
13
14// Usage
15const order = await getOrderStatus(token, orgId, merchantId, 'ord_abc123');
16console.log('Current status:', order.status);
17console.log('Amount:', order.amountIn, order.originCurrencySymbol);

Response:

1{
2 "id": "ord_abc123xyz",
3 "type": "PAYIN",
4 "status": "COMPLETED",
5 "amountIn": 50000,
6 "originCurrencySymbol": "COP",
7 "destinationCurrencySymbol": "COP",
8 "externalId": "order-12345",
9 "dates": {
10 "confirmationDate": "2025-11-13T10:00:00Z",
11 "paymentDate": "2025-11-13T10:05:00Z",
12 "deliveryDate": "2025-11-13T10:06:00Z"
13 }
14}

Complete Integration Example

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

Node.js
1const axios = require('axios');
2
3const BASE_URL = 'https://api-sandbox.koywe.com/api/v1';
4const ORG_ID = process.env.KOYWE_ORG_ID;
5const MERCHANT_ID = process.env.KOYWE_MERCHANT_ID;
6const API_KEY = process.env.KOYWE_API_KEY;
7const SECRET = process.env.KOYWE_SECRET;
8
9async function acceptPayment(customerData, orderDetails) {
10 try {
11 // 1. Authenticate
12 console.log('1. Authenticating...');
13 const authResponse = await axios.post(`${BASE_URL}/auth/sign-in`, {
14 apiKey: API_KEY,
15 secret: SECRET
16 });
17 const token = authResponse.data.token;
18 console.log('✓ Authenticated');
19
20 const headers = {
21 'Authorization': `Bearer ${token}`,
22 'Content-Type': 'application/json'
23 };
24
25 // 2. Get payment methods
26 console.log('\n2. Getting payment methods...');
27 const methodsResponse = await axios.get(
28 `${BASE_URL}/payment-methods`,
29 {
30 params: {
31 countrySymbol: customerData.country,
32 currencySymbol: orderDetails.currency
33 },
34 headers: { 'Authorization': `Bearer ${token}` }
35 }
36 );
37 const methods = methodsResponse.data;
38 console.log('✓ Available methods:', methods.map(m => m.method).join(', '));
39
40 // 3. Create contact
41 console.log('\n3. Creating customer contact...');
42 const contactResponse = await axios.post(
43 `${BASE_URL}/organizations/${ORG_ID}/merchants/${MERCHANT_ID}/contacts`,
44 {
45 email: customerData.email,
46 phone: customerData.phone,
47 fullName: customerData.fullName,
48 countrySymbol: customerData.country,
49 documentType: customerData.documentType,
50 documentNumber: customerData.documentNumber,
51 businessType: 'PERSON'
52 },
53 { headers }
54 );
55 const contactId = contactResponse.data.id;
56 console.log('✓ Contact created:', contactId);
57
58 // 4. Create PAYIN order
59 console.log('\n4. Creating payment order...');
60 const orderResponse = await axios.post(
61 `${BASE_URL}/organizations/${ORG_ID}/merchants/${MERCHANT_ID}/orders`,
62 {
63 type: 'PAYIN',
64 originCurrencySymbol: orderDetails.currency,
65 destinationCurrencySymbol: orderDetails.currency,
66 amountIn: orderDetails.amount,
67 description: orderDetails.description,
68 externalId: orderDetails.orderId,
69 contactId: contactId,
70 paymentMethods: [
71 {
72 method: orderDetails.paymentMethod,
73 extra: orderDetails.bank
74 }
75 ],
76 successUrl: 'https://yoursite.com/payment/success',
77 failedUrl: 'https://yoursite.com/payment/failed'
78 },
79 { headers }
80 );
81 const order = orderResponse.data;
82 console.log('✓ Order created:', order.id);
83 console.log('✓ Status:', order.status);
84
85 // 5. Return payment URL
86 console.log('\n5. Payment URL ready:');
87 console.log(order.paymentUrl);
88
89 return {
90 success: true,
91 orderId: order.id,
92 paymentUrl: order.paymentUrl,
93 status: order.status
94 };
95
96 } catch (error) {
97 console.error('❌ Error:', error.response?.data || error.message);
98 throw error;
99 }
100}
101
102// Usage
103const result = await acceptPayment(
104 {
105 email: 'customer@example.com',
106 phone: '+573001234567',
107 fullName: 'Juan Pérez',
108 country: 'CO',
109 documentType: 'CC',
110 documentNumber: '1234567890'
111 },
112 {
113 currency: 'COP',
114 amount: 50000,
115 description: 'Payment for Order #12345',
116 orderId: `order-${Date.now()}`,
117 paymentMethod: 'PSE',
118 bank: 'BANCOLOMBIA'
119 }
120);
121
122console.log('\n✅ Complete! Redirect customer to:');
123console.log(result.paymentUrl);

Error Handling

Common Errors and Solutions

Insufficient Balance

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

Invalid Payment Method

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

Invalid Document

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

Expired Token

1// Error: 401 Unauthorized
2// Solution: Implement token refresh
3if (error.response?.status === 401) {
4 token = await authenticate();
5 // Retry request
6}

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