Koywe
On this page

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:

Text
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
ColombiaCED_CIU1020304050
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:

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

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

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

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

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

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

JavaScript
{
  "method": "CARD"
}

Chile (CLP) 🇨🇱

Khipu

Usage:

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

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

JavaScript
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.providedAction);
  
  // 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:

Text
PENDING → PROCESSING → PAID → COMPLETED

Failed Payment Scenario

Test payment failure handling:

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

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

Text
PENDING → PROCESSING → FAILED

Expired Payment

Test order expiration:

JavaScript
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.providedAction);
  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.providedAction);

Testing the checkout flow:

  1. Create the payment link using the code above
  2. Copy the providedAction
  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. - Instant webhook URL
  2. - Tunnel to localhost
  3. - Webhook inspector

Using webhook.site

  1. Get webhook URL

    Visit and copy your unique URL

  2. Create order with webhook

    JavaScript
    const order = await createPayinOrder(token, orgId, merchantId, {
      amount: 50000,
      webhookUrl: 'https://webhook.site/your-unique-id'
    });
  3. 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

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

JavaScript
{
  "firstName": "Juan",
  "lastName": "Pérez Test",
  "countrySymbol": "CO",
  "businessType": "PERSON",
  "type": "PERSON",
  "documentType": "CED_CIU",
  "documentNumber": "1234567890"
}

Brazil

JavaScript
{
  "firstName": "Maria",
  "lastName": "Silva Test",
  "countrySymbol": "BR",
  "businessType": "PERSON",
  "type": "PERSON",
  "documentType": "CPF",
  "documentNumber": "12345678900"
}

Mexico

JavaScript
{
  "firstName": "Pedro",
  "lastName": "García Test",
  "countrySymbol": "MX",
  "businessType": "PERSON",
  "type": "PERSON",
  "documentType": "RFC",
  "documentNumber": "XAXX010101000"
}

Chile

JavaScript
{
  "firstName": "Ana",
  "lastName": "López Test",
  "countrySymbol": "CL",
  "businessType": "PERSON",
  "type": "PERSON",
  "documentType": "RUT",
  "documentNumber": "11111111-1"
}

Test Bank Accounts

For PAYOUT testing:

Colombia

JavaScript
{
  "name": "Bancolombia COP",
  "kind": "BANK",
  "isDefault": true,
  "countrySymbol": "CO",
  "currencySymbol": "COP",
  "entity": "BANCOLOMBIA",
  "accountNumber": "1234567890",
  "type": "SAVINGS"
}

Brazil

JavaScript
{
  "name": "Banco do Brasil BRL",
  "kind": "BANK",
  "isDefault": true,
  "countrySymbol": "BR",
  "currencySymbol": "BRL",
  "entity": "BANCO_DO_BRASIL",
  "accountNumber": "12345678",
  "type": "CHECKING"
}

Mexico

JavaScript
{
  "name": "BBVA Mexico MXN",
  "kind": "BANK",
  "isDefault": true,
  "countrySymbol": "MX",
  "currencySymbol": "MXN",
  "entity": "BBVA_MEXICO",
  "accountNumber": "012345678901234567",
  "type": "CHECKING"
}

Chile

JavaScript
{
  "name": "Banco Chile CLP",
  "kind": "BANK",
  "isDefault": true,
  "countrySymbol": "CL",
  "currencySymbol": "CLP",
  "entity": "BANCO_CHILE",
  "accountNumber": "12345678",
  "type": "CHECKING"
}

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 Testnet
ETHEREUMSepolia
POLYGONAmoy
BSCBSC Testnet

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

Same currency support as production: Which symbols each network accepts does not change in sandbox — see Supported Networks.

Test Wallet Address

Use this address for receiving test crypto:

Text
0x0000000000000000000000000000000000000000

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

ONRAMP Test

JavaScript
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

JavaScript
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

JavaScript
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

JavaScript
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, {
    firstName: 'Test',
    lastName: 'Customer',
    countrySymbol: 'CO',
    businessType: 'PERSON',
    type: 'PERSON',
    email: 'test@example.com',
    documentType: 'CED_CIU',
    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.providedAction);
  
  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

JavaScript
async function testPayoutFlow() {
  console.log('1. Create provider contact...');
  const provider = await createContact(token, orgId, merchantId, {
    firstName: 'Test',
    lastName: 'Provider',
    countrySymbol: 'CO',
    businessType: 'COMPANY',
    type: 'BUSINESS',
    email: 'provider@example.com',
    documentType: 'NIT',
    documentNumber: '9001234561'
  });
  
  console.log('2. Add provider bank account...');
  const bankAccount = await addBankAccountToContact(token, orgId, merchantId, provider.id, {
    name: 'Bancolombia COP',
    kind: 'BANK',
    isDefault: true,
    countrySymbol: 'CO',
    currencySymbol: 'COP',
    entity: 'BANCOLOMBIA',
    accountNumber: '1234567890',
    type: '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.

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

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

Request

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

Response

JSON
{
  "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.
  • virtualAccountProvisioned — true the first time a virtual account had to be created for the currency; false on subsequent calls.

Example

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

  1. Request Production Credentials

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

  2. Update Base URL

    Change from sandbox to production:

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

    Replace sandbox credentials with production credentials in your environment variables

  4. Update Webhook URLs

    Change webhook endpoints from test URLs to production URLs

  5. Test with Small Amounts

    Start with small real transactions to verify everything works

  6. Monitor Closely

    Watch your first production transactions carefully

  7. 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?