Skip to Content
Getting StartedTesting Guide

Testing in Sandbox

The Koywe sandbox environment allows you to test all payment flows with simulated transactions before going live.

Sandbox Environment

Base URL

All sandbox API requests use:

https://api-sandbox.koywe.com

Getting Test Credentials

Contact soporte@koywe.com to receive:

  • Sandbox API Key
  • Sandbox Secret
  • Organization ID
  • Merchant ID

Sandbox vs Production: Sandbox credentials are completely separate from production. No real money is involved in sandbox testing.


Sandbox behavior differences

Sandbox mirrors production closely, but a handful of behaviors differ in ways that trip up first-time integrators. Budget time for these up front:

BehaviorWhat actually happens in sandbox
PAYOUT ordersCreate as PENDING, then transition to FAILED. USD is deducted and then refunded. The payout service itself does not execute — use this to test the state-machine, not the external wire.
KHIPU PAYINAuto-completes on the Koywe side without you visiting the Khipu link.
QRI payment method (CLP)Broken in sandbox — use KHIPU instead.
BALANCE_TRANSFER without a policyBlocked with POL00002. Create a policy and an ALLOW rule first (see below).
BALANCE_TRANSFER with a policyCompletes instantly — no intermediate PROCESSING state.
Invalid document numbersAlways enforced, including in sandbox (DC00010). Tests need valid document numbers per country (see the seed list below).
Payout destination currency mismatchBAA00008 — the destination account’s currency must exactly match the order’s destination currency.
Wrong organizationId in configGETs silently return empty; POSTs fail with MC00015 (“Merchant does not belong to the organization”). If reads work but writes fail on a fresh setup, check this first.
MFA-gated operationsOrders transition into ON_HOLD and wait (POL00007). Approve via the dashboard or pass --mfa-token / use flow order --wait.

Seed test document numbers

Document validation (DC00010) is enforced in sandbox. The values below pass the country-specific format checks and are safe for tests:

CountryDocument typeExample
ChileRUT11111111-1
Brazil (individual)CPF11144477735
Brazil (company)CNPJ11222333000181
ColombiaCC1020304050
MexicoRFCXAXX010101000
ArgentinaCUIT20123456780
PeruDNI12345678

Minimum viable policy for BALANCE_TRANSFER

Before you can create a BALANCE_TRANSFER or any MFA-gated order in sandbox, the organization needs an active policy with a matching rule:

npx @koyweforest/cli policy create --data '{"name":"default"}' npx @koyweforest/cli policy rules create --data '{ "name": "allow-balance-transfer", "scope": "ORDER", "match": { "orderType": ["BALANCE_TRANSFER"] }, "decision": { "action": "ALLOW" } }'

See Passkeys & Approvals for the full policy model and Error Code Catalog for POL* codes.


Test Payment Methods by Country

Colombia (COP) 🇨🇴

PSE (Pagos Seguros en Línea)

Test Banks:

  • BANCOLOMBIA
  • DAVIVIENDA
  • BOGOTA
  • OCCIDENTE

Usage:

{ "method": "PSE", "extra": { "bankAccount": { "name": "BANCOLOMBIA" } } // Any test bank }

Behavior:

  • Payment link is fully functional in sandbox
  • Follow the simulated payment process
  • You can choose to succeed or fail the payment during the flow
  • Allows testing of complete user experience

Nequi

Usage:

{ "method": "NEQUI" }

Behavior:

  • Generates a test QR code
  • After scanning the QR code, you’ll be given options to succeed or fail the payment
  • Simulates the complete Nequi payment experience

Brazil (BRL) 🇧🇷

PIX Static

Usage:

{ "method": "PIX_STATIC" }

Behavior:

  • Generates a test QR code
  • After scanning the QR code, you’ll be given options to succeed or fail the payment
  • Simulates the complete PIX payment experience

PIX Dynamic

Usage:

{ "method": "PIX_DYNAMIC" }

Behavior:

  • Generates a test QR code
  • After scanning the QR code, you’ll be given options to succeed or fail the payment
  • Similar to PIX_STATIC with interactive testing options

Mexico (MXN) 🇲🇽

SPEI (Instant Settlement ⚡)

Usage:

{ "method": "SPEI" }

Behavior:

  • Provides test bank account details
  • Auto-completes after order creation
  • Simulates bank transfer confirmation

Cards

Test Card Numbers:

  • Success: 4242424242424242
  • Decline: 4000000000000002
  • Insufficient Funds: 4000000000009995

Usage:

{ "method": "CARD" }

Chile (CLP) 🇨🇱

Khipu

Usage:

{ "method": "KHIPU" }

Behavior:

  • Payment link is fully functional in sandbox
  • Follow the simulated payment process
  • You can choose to succeed or fail the payment during the flow
  • Allows testing of complete user experience

Argentina (ARS) 🇦🇷

Multiple Local Methods

Usage:

{ "method": "KHIPU" // Also works for Argentina }

Behavior:

  • Payment link is fully functional in sandbox
  • You can choose to succeed or fail the payment during the flow

Test Scenarios

Interactive Testing: Most payment methods (PSE, Khipu) provide functional payment links where you can follow the simulated payment process and choose to succeed or fail. QR-based methods (PIX, Nequi) give you options after scanning the code.

Successful Payment Flow

Test a successful end-to-end payment:

async function testSuccessfulPayment() { // Any amount will succeed in sandbox const order = await createPayinOrder(token, orgId, merchantId, { amount: 50000, currency: 'COP', paymentMethod: 'PSE', bank: 'BANCOLOMBIA' }); console.log('Order created:', order.id); console.log('Status:', order.status); // "PENDING" console.log('Payment URL:', order.paymentUrl); // In sandbox, order auto-completes // Wait a few seconds and check status await sleep(5000); const updated = await getOrderStatus(token, orgId, merchantId, order.id); console.log('Updated status:', updated.status); // "COMPLETED" }

Expected flow:

PENDING → PROCESSING → PAID → COMPLETED

Failed Payment Scenario

Test payment failure handling:

Use the special test amount 666 to simulate a failed payment:

async function testFailedPayment() { const order = await createPayinOrder(token, orgId, merchantId, { amount: 666, // Special test amount for failure currency: 'COP', paymentMethod: 'PSE', bank: 'BANCOLOMBIA' }); console.log('Order created:', order.id); // Wait for processing await sleep(3000); const updated = await getOrderStatus(token, orgId, merchantId, order.id); console.log('Status:', updated.status); // "FAILED" console.log('Error:', updated.errorMessage); }

Expected flow:

PENDING → PROCESSING → FAILED

Expired Payment

Test order expiration:

async function testExpiredPayment() { // Set dueDate in the past const pastDate = new Date(); pastDate.setHours(pastDate.getHours() - 1); const order = await createPayinOrder(token, orgId, merchantId, { amount: 50000, currency: 'COP', paymentMethod: 'PSE', bank: 'BANCOLOMBIA', dueDate: pastDate.toISOString() }); console.log('Status:', order.status); // "EXPIRED" }

Test the complete Koywe-branded checkout experience:

PAYMENT_LINK is perfect for e-commerce! No contact or payment method needed - just create a link and share it. The customer enters their own data and selects their payment method in the Koywe checkout.

async function testPaymentLink() { // Create payment link - no contact or payment method needed! const order = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`, { type: 'PAYMENT_LINK', originCurrencySymbol: 'COP', destinationCurrencySymbol: 'COP', amountIn: 50000, description: 'Test Invoice #123 - Web Design', externalId: `test-invoice-${Date.now()}` }, { headers: { 'Authorization': `Bearer ${token}` } } ); console.log('✓ Payment link created:', order.data.id); console.log('📧 Share this link:', order.data.paymentUrl); console.log(''); console.log('Customer will:'); console.log(' 1. Open the link'); console.log(' 2. See Koywe-branded checkout'); console.log(' 3. Enter their personal information'); console.log(' 4. Select payment method (PSE, PIX, Nequi, etc.)'); console.log(' 5. Complete payment'); // Simulate sharing via email, WhatsApp, or QR code return order.data; } // Usage const link = await testPaymentLink(); // Open the payment URL in a browser to test the full checkout flow console.log('\n🌐 Open this URL to test:', link.paymentUrl);
def test_payment_link(): # Create payment link - minimal fields! response = requests.post( f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/orders', json={ 'type': 'PAYMENT_LINK', 'originCurrencySymbol': 'COP', 'destinationCurrencySymbol': 'COP', 'amountIn': 50000, 'description': 'Test Invoice #123 - Web Design' }, headers={'Authorization': f'Bearer {token}'} ) order = response.json() print(f"✓ Payment link created: {order['id']}") print(f"📧 Share this link: {order['paymentUrl']}") return order # Usage link = test_payment_link() print(f"\n🌐 Open this URL to test: {link['paymentUrl']}")

Testing the checkout flow:

  1. Create the payment link using the code above
  2. Copy the paymentUrl
  3. Open it in your browser
  4. You’ll see the Koywe-branded checkout page
  5. Fill in test customer information
  6. Select a payment method
  7. Complete the simulated payment (choose success or fail)
  8. Verify webhook notification is received

Use Case: PAYMENT_LINK is ideal for:

  • E-commerce checkout pages
  • Invoice payment requests
  • Email/WhatsApp payment links
  • QR code payments
  • Quick payment collections without storing customer data

Testing Webhooks

Setup Webhook Endpoint

Use webhook testing tools:

  1. webhook.site  - Instant webhook URL
  2. ngrok  - Tunnel to localhost
  3. RequestBin  - Webhook inspector

Using webhook.site

Get webhook URL

Visit webhook.site  and copy your unique URL

Create order with webhook

const order = await createPayinOrder(token, orgId, merchantId, { amount: 50000, webhookUrl: 'https://webhook.site/your-unique-id' });

View webhooks

Return to webhook.site to see webhook events in real-time

Expected Webhook Events

For a successful PAYIN:

  1. order.created - Order is created
  2. order.pending - Waiting for payment
  3. order.processing - Payment being processed
  4. order.paid - Payment confirmed
  5. order.completed - Funds credited

Webhook Payload Example

{ "id": "evt_abc123", "type": "order.completed", "version": "v1", "occurred_at": "2025-11-13T15:30:00Z", "source": "koywe.api", "environment": "sandbox", "organization_id": "org_xyz", "merchant_id": "mrc_abc", "data": { "orderId": "ord_123456", "type": "PAYIN", "status": "COMPLETED", "amountIn": 50000, "currencySymbol": "COP" } }

Test Data

Test Contacts

Use these test document numbers (all valid in sandbox):

Colombia

{ "documentType": "CC", "documentNumber": "1234567890", "fullName": "Juan Pérez Test" }

Brazil

{ "documentType": "CPF", "documentNumber": "12345678900", "fullName": "Maria Silva Test" }

Mexico

{ "documentType": "RFC", "documentNumber": "XAXX010101000", "fullName": "Pedro García Test" }

Chile

{ "documentType": "RUT", "documentNumber": "11111111-1", "fullName": "Ana López Test" }

Test Bank Accounts

For PAYOUT testing:

Colombia

{ "bankCode": "BANCOLOMBIA", "accountNumber": "1234567890", "accountType": "savings", "currencySymbol": "COP" }

Brazil

{ "bankCode": "BANCO_DO_BRASIL", "accountNumber": "12345678", "accountType": "checking", "currencySymbol": "BRL" }

Mexico

{ "bankCode": "BBVA_MEXICO", "accountNumber": "012345678901234567", "accountType": "checking", "currencySymbol": "MXN" }

Chile

{ "bankCode": "BANCO_CHILE", "accountNumber": "12345678", "accountType": "checking", "currencySymbol": "CLP" }

Testing Crypto Operations

Automatic Test Network Selection

Production Network Names in Sandbox: When using production network names like ETHEREUM, POLYGON, or BSC in sandbox, the system automatically routes to test networks (Sepolia, Amoy, BSC Testnet respectively). You don’t need to specify testnet names explicitly.

Test Networks

Sandbox automatically uses these testnets:

Production NetworkSandbox TestnetSupported Assets
ETHEREUMSepoliaETH, USDC, USDT
POLYGONAmoyMATIC, USDC
BSCBSC TestnetBNB, USDC

Example: Specify "network": "POLYGON" in your request, and sandbox will use Amoy automatically.

Test Wallet Address

Use this address for receiving test crypto:

0x0000000000000000000000000000000000000000

Never use production addresses in sandbox - Always use test/burn addresses like the zero address above.

ONRAMP Test

async function testOnramp() { // Create crypto wallet (or use existing) const wallet = await createCryptoWallet(token, orgId, merchantId, { address: '0x0000000000000000000000000000000000000000', network: 'ETHEREUM' // Automatically uses Sepolia in sandbox }); // Buy USDC with COP const order = await createOrder(token, orgId, merchantId, { type: 'ONRAMP', originCurrencySymbol: 'COP', destinationCurrencySymbol: 'USDC', amountIn: 50000, destinationAccountId: wallet.id }); console.log('ONRAMP order:', order.id); // In sandbox, crypto is "sent" to test address }

OFFRAMP Test

async function testOfframp() { // Sell USDC for COP const order = await createOrder(token, orgId, merchantId, { type: 'OFFRAMP', originCurrencySymbol: 'USDC', destinationCurrencySymbol: 'COP', amountIn: 10 // 10 USDC }); console.log('OFFRAMP order:', order.id); // In sandbox, order auto-completes // Fiat is credited to virtual account }

Rate Limits

Sandbox environment has the following limits:

Limit TypeValue
Requests per minute100
Requests per hour1,000
Orders per day10,000

Rate limit headers are included in all responses:

  • X-RateLimit-Limit: Total requests allowed
  • X-RateLimit-Remaining: Requests remaining
  • X-RateLimit-Reset: Unix timestamp when limit resets

Testing Best Practices

Test All Order Types

Test each order type:

  • ✅ PAYIN with different payment methods
  • ✅ PAYOUT to different countries
  • ✅ BALANCE_TRANSFER between currencies
  • ✅ ONRAMP for crypto purchases
  • ✅ OFFRAMP for crypto sales
  • ✅ PAYMENT_LINK with expiry

Test Error Scenarios

Test error handling:

  • ✅ Invalid API credentials
  • ✅ Insufficient balance for PAYOUT
  • ✅ Invalid bank account details
  • ✅ Expired quotes
  • ✅ Failed payments (amount: 666)
  • ✅ Webhook signature verification

Test Idempotency

async function testIdempotency() { const externalId = `test-order-${Date.now()}`; // Create order const order1 = await createPayinOrder(token, orgId, merchantId, { amount: 50000, externalId: externalId }); // Try to create same order again const order2 = await createPayinOrder(token, orgId, merchantId, { amount: 50000, externalId: externalId // Same externalId }); // Should return same order console.log(order1.id === order2.id); // true }

Common Test Workflows

E-Commerce Checkout Flow

async function testCheckoutFlow() { console.log('1. Customer adds items to cart...'); const cartTotal = 50000; // 50,000 COP console.log('2. Customer proceeds to checkout...'); const contact = await createContact(token, orgId, merchantId, { email: 'test@example.com', fullName: 'Test Customer', country: 'CO', documentType: 'CC', documentNumber: '1234567890' }); console.log('3. Get payment methods...'); const methods = await getPaymentMethods('CO', 'COP'); console.log('4. Create payment order...'); const order = await createPayinOrder(token, orgId, merchantId, { amount: cartTotal, currency: 'COP', contactId: contact.id, paymentMethod: 'PSE', bank: 'BANCOLOMBIA', externalId: `cart-${Date.now()}` }); console.log('5. Redirect customer to payment...'); console.log('Payment URL:', order.paymentUrl); console.log('6. Wait for webhook confirmation...'); // In production, webhook handler processes this await sleep(5000); console.log('7. Verify order completed...'); const completed = await getOrderStatus(token, orgId, merchantId, order.id); console.log('Final status:', completed.status); // "COMPLETED" console.log('8. Fulfill order...'); console.log('✅ Test checkout flow complete!'); }

Provider Payout Flow

async function testPayoutFlow() { console.log('1. Create provider contact...'); const provider = await createContact(token, orgId, merchantId, { email: 'provider@example.com', fullName: 'Test Provider', country: 'CO', documentType: 'NIT', documentNumber: '900123456-1', businessType: 'COMPANY' }); console.log('2. Add provider bank account...'); const bankAccount = await addBankAccountToContact(token, orgId, merchantId, provider.id, { country: 'CO', currency: 'COP', bankCode: 'BANCOLOMBIA', accountNumber: '1234567890', accountType: 'checking' }); console.log('3. Check virtual balance...'); const balance = await getBalance(token, orgId, merchantId, 'COP'); console.log('Available:', balance.availableBalance, 'COP'); console.log('4. Create payout order...'); const payout = await createPayoutOrder(token, orgId, merchantId, { amount: 100000, currency: 'COP', contactId: provider.id, destinationAccountId: bankAccount.id, externalId: `payout-${Date.now()}` }); console.log('5. Monitor payout status...'); await sleep(3000); const completed = await getOrderStatus(token, orgId, merchantId, payout.id); console.log('Final status:', completed.status); // "COMPLETED" console.log('✅ Test payout flow complete!'); }

Simulating Bank Income (Sandbox Only)

Some flows — SPEI deposits, direct bank transfers, certain ARS/MXN rails — work by receiving funds into a virtual bank account provisioned for your merchant. In sandbox, there’s no real bank wire to trigger these. Use the bank-income simulator to register a fake incoming deposit that runs through the normal bank-income processor, exactly as a real wire would.

POST /api/v1/organizations/{organizationId}/merchants/{merchantId}/sandbox/bank-income/simulate

This endpoint is sandbox-only. It returns an error in production.

Request

{ "currency": "ARS", "amount": 500000, "documentType": "CUIT", "documentNumber": "30712345678", "customerReference": "customer-test-001" }
FieldRequiredDescription
currencyARS, MXN, or USD. Selects which fake virtual account is used.
amountAmount to credit.
documentTypeConditionalDepositor document type. Required for ARS and MXN unless validation is bypassed by merchant flags.
documentNumberConditionalDepositor document number. Same conditions as documentType.
customerReferenceFree-form reference to make the simulated movement easy to trace in reports.

Response

{ "referenceId": "bm_1234567890", "virtualAccount": { /* PayInVirtualBankAccount */ }, "virtualAccountProvisioned": true }
  • referenceId — ID of the registered bank income, usable for reconciliation.
  • virtualAccount — The virtual account the deposit landed in. If the merchant didn’t already have one for this currency, the simulator provisions one on the fly.
  • virtualAccountProvisionedtrue the first time a virtual account had to be created for the currency; false on subsequent calls.

Example

async function simulateSandboxDeposit() { const response = await axios.post( `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/sandbox/bank-income/simulate`, { currency: 'ARS', amount: 500000, documentType: 'CUIT', documentNumber: '30712345678', customerReference: `test-${Date.now()}` }, { headers: { Authorization: `Bearer ${token}` } } ); console.log('Simulated deposit:', response.data.referenceId); console.log('Credited to VA:', response.data.virtualAccount.accountNumber); }

Use cases:

  • Fund a sandbox merchant so you can test PAYOUT, BALANCE_TRANSFER, or OFFRAMP without going through a full PAYIN flow first.
  • Reproduce bank-income edge cases (unknown depositor, document mismatch) by varying the document fields.
  • Smoke-test reconciliation pipelines that consume bank-income events.

Moving to Production

When you’re ready to go live:

Request Production Credentials

Contact soporte@koywe.com for production API key and secret

Update Base URL

Change from sandbox to production:

// Sandbox const BASE_URL = 'https://api-sandbox.koywe.com'; // Production const BASE_URL = 'https://api.koywe.com';

Update Credentials

Replace sandbox credentials with production credentials in your environment variables

Update Webhook URLs

Change webhook endpoints from test URLs to production URLs

Test with Small Amounts

Start with small real transactions to verify everything works

Monitor Closely

Watch your first production transactions carefully

Setup Monitoring

Implement logging, alerts, and monitoring for production

Production Checklist

  • Production API credentials obtained
  • Base URL updated to production
  • Webhook URLs pointing to production servers
  • Webhook signature verification implemented
  • Error handling tested
  • Logging and monitoring setup
  • Small test transaction successful
  • Team trained on production processes

Troubleshooting

Order Stuck in PENDING

Issue: Order created but stays in PENDING status

Solution:

  • In sandbox, wait 30-60 seconds for auto-completion
  • Check if you used the failure test amount (666)
  • Verify payment method is correct for country

Webhooks Not Received

Issue: No webhook events arriving

Solution:

  • Verify webhook URL is publicly accessible
  • Check firewall/security settings
  • Use webhook.site to test
  • Verify webhook endpoint is configured

Insufficient Balance Error

Issue: PAYOUT fails with insufficient balance

Solution:

  • Check virtual account balance
  • Create PAYIN orders to add funds to sandbox account
  • Verify you’re checking the correct currency balance

Need Help?

Last updated on