Skip to main content

Sandbox Environment

Test your integration thoroughly in our sandbox environment before going live. No real money, no risk. Our sandbox works exactly like production - same API, same behavior:
https://api.cheqpay.dev/pos

Sandbox Features

No Real Money

Process test payments without financial risk

Test Cards

Use pre-defined test card numbers for success, decline, and 3DS scenarios

Full Feature Parity

All production features available in sandbox

Faster Processing

Instant processing for rapid testing
Offline payment methods (SPEI, PayCash, and CIE Cash Net) use separate sandbox flows — they do not use test card numbers. See the guides below:
Payment methodSandbox testingFeature guide
SPEIMock transfers can auto-complete; use webhook simulators for STP/OPMSPEI Transfers
PayCashDoes not auto-complete; use the PayCash simulatorPayCash Payments
CIE Cash NetDoes not auto-complete; use the CIE Cash Net simulatorCIE Cash Net Payments

Test Cards

Use these cards to test different scenarios:

Successful Payments

Card NumberBrandBehavior
4000000000002503VisaSuccessful payment
5200000000002151MastercardSuccessful payment
For 3D Secure (3DS) testing, see the complete 3DS testing guide which includes specific test cards and amount thresholds for different 3DS scenarios.

Failed Payments

In sandbox, declines are triggered by the transaction amount, not the card number. You can use any of the test card numbers listed under “Successful Payments” (or a generic test card) — what determines the response is the amount you submit.

How it works

The amount must end in two specific digits that map to a known decline reason. Use a base amount of 9000.xx where xx is the response code you want to trigger. The same trigger amounts apply to all supported currencies (MXN, USD) in sandbox.
  • Amounts in the API are submitted as integers in the smallest currency unit (cents).
  • Example (MXN): to trigger “Expired card” (code 33), submit amount: 900033 with currency: "MXN" (= 9000.33 MXN).
  • Example (USD): to trigger “No sufficient funds” (code 51), submit amount: 900051 with currency: "USD" (= 9000.51 USD).
This amount-based trigger mechanism is only available in sandbox. In production, the same amounts are processed normally and will not generate synthetic declines.
Any other amount that does not match an entry below will be approved. To test a successful payment, use any amount that does not end in one of the listed two-digit codes.

Decline trigger amounts

Amount (MXN)API value (cents)Decline reason
9000.01900001Refer to card issuer
9000.02900002Refer to special conditions for card issuer
9000.03900003Invalid merchant
9000.04900004Pick-up card
9000.05900005Do not honor
9000.06900006Error
9000.07900007Pick-up card, special condition
9000.09900009Request in progress (duplicate)
9000.12900012Invalid transaction
9000.13900013Invalid amount
9000.14900014Invalid card number (no such number)
9000.15900015No such issuer
9000.17900017Customer cancellation
9000.22900022Suspected malfunction
9000.30900030Format error
9000.31900031Bank not supported by switch
9000.33900033Expired card
9000.34900034Suspected fraud
9000.35900035Card acceptor contact acquirer
9000.36900036Restricted card
9000.37900037Card acceptor call acquirer security
9000.38900038Allowable PIN tries exceeded
9000.39900039No credit account
9000.40900040Command rejected
9000.41900041Lost card
9000.43900043Stolen card, pick-up
9000.51900051No sufficient funds
9000.54900054Expired card
9000.55900055Incorrect personal identification number
9000.56900056No card record
9000.57900057Transaction not permitted to cardholder
9000.58900058Transaction not permitted to terminal
9000.61900061Exceeds withdrawal amount limit
9000.62900062Restricted card
9000.65900065Exceeds withdrawal frequency limit
9000.68900068Response received too late

Test Card Details

For all test cards:
  • Expiry Date: Any future date (e.g., 12/2025)
  • CVC: Any 3 digits (e.g., 123)
  • Billing Address: Any valid address
Test cards work only in sandbox. They will be rejected in production.
Consistency between runs: because the response is determined by the amount, you must keep the amount fixed across executions of the same scenario. Changing the amount will change the response — even if the card stays the same.

Test 3D Secure

Test the complete 3DS authentication flow using specific test cards and amount thresholds.

Trigger 3DS Challenge

Use amounts >= 100,000 cents (1,000 MXN) with the 3DS test card:
curl -X POST https://api.cheqpay.dev/pos/payment-orders \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "test-3ds-001",
    "amount": 100000,
    "currency": "MXN",
    "paymentMethod": {
      "type": "card",
      "cardDetails": {
        "number": "4000000000002503",
        "expiryMonth": "12",
        "expiryYear": "25",
        "cvc": "123"
      }
    }
  }'
3DS Test Cards:
  • 4000000000002503 with amount >= 100,000 cents triggers full 3DS challenge
  • 4000000000002701 with amount >= 100,000 cents triggers frictionless 3DS
  • 4000000000002701 with amount < 100,000 cents skips 3DS entirely
See the complete 3DS testing guide for all scenarios.

Complete Authentication

When prompted for a verification code in sandbox, use:
123456
This simulates successful customer authentication.

Complete 3DS Guide

Learn how to implement 3D Secure authentication

Test Refunds

Refunds process instantly in sandbox (instead of 5-10 days in production):
# Create a successful payment
PAYMENT_ID=$(curl -X POST https://api.cheqpay.dev/pos/payment-orders \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -d '{"externalId": "test-001", "amount": 10000, ...}' \
  | jq -r '.paymentOrder.id')

# Issue a refund
curl -X POST https://api.cheqpay.dev/pos/payment-orders/$PAYMENT_ID/refund \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -d '{"reason": "Test refund"}'

Common Test Scenarios

Scenario 1: Successful Card Payment

curl -X POST https://api.cheqpay.dev/pos/payment-orders \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "test-001",
    "customer": {
      "firstName": "Test",
      "lastName": "User",
      "email": "test@example.com"
    },
    "amount": 10000,
    "currency": "MXN",
    "paymentMethod": {
      "type": "card",
      "cardDetails": {
        "number": "4000000000002503",
        "expiryMonth": "12",
        "expiryYear": "25",
        "cvc": "123"
      }
    }
  }'
Expected: Status COMPLETED, payment successful.

Scenario 2: Declined Payment

Trigger a decline by submitting one of the amounts listed in the Failed Payments table. Any test card works — the amount is what determines the response. The example below triggers Expired card (code 33) by submitting amount: 900033 (= 9000.33 MXN):
curl -X POST https://api.cheqpay.dev/pos/payment-orders \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "test-002",
    "amount": 900033,
    "currency": "MXN",
    "paymentMethod": {
      "type": "card",
      "cardDetails": {
        "number": "4000000000002503",
        "expiryMonth": "12",
        "expiryYear": "25",
        "cvc": "123"
      }
    }
  }'
Expected: Status FAILED with Expired card reason. To test a different decline reason, change the last two digits of the amount field to match the desired code from the table.

Same scenario in USD

The same trigger amounts work for any supported currency. The example below triggers No sufficient funds (code 51) in USD by submitting amount: 900051 with currency: "USD":
curl -X POST https://api.cheqpay.dev/pos/payment-orders \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "test-002-usd",
    "amount": 900051,
    "currency": "USD",
    "paymentMethod": {
      "type": "card",
      "cardDetails": {
        "number": "4000000000002503",
        "expiryMonth": "12",
        "expiryYear": "25",
        "cvc": "123"
      }
    }
  }'
Expected: Status FAILED with No sufficient funds reason.

Scenario 3: 3DS Authentication Flow

  1. Create payment with 3DS card
  2. Receive PAYER_AUTHENTICATION_CHALLENGE_REQUIRED status
  3. Display authentication challenge
  4. Customer enters code 123456
  5. Validate authentication
  6. Payment completes

Scenario 4: Save & Reuse Card

# 1. Save card during payment
curl -X POST https://api.cheqpay.dev/pos/payment-orders \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -d '{
    "externalId": "test-003",
    "amount": 10000,
    "currency": "MXN",
    "paymentMethod": {
      "type": "card",
      "cardDetails": {
        "number": "4000000000002503",
        "expiryMonth": "12",
        "expiryYear": "25",
        "cvc": "123"
      },
      "persist": true
    }
  }'

# 2. Get paymentMethodId from response
# 3. Use saved card for next payment
curl -X POST https://api.cheqpay.dev/pos/payment-orders \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -d '{
    "externalId": "test-004",
    "amount": 5000,
    "currency": "MXN",
    "paymentMethod": {
      "type": "payment_method_id",
      "paymentMethodId": "pm_xxx",
      "cvc": "123"
    }
  }'

Scenario 5: Idempotency Test

Send the same request twice with same externalId:
# First request
curl -X POST https://api.cheqpay.dev/pos/payment-orders \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -d '{"externalId": "test-005", "amount": 10000, ...}'

# Second request (duplicate)
curl -X POST https://api.cheqpay.dev/pos/payment-orders \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -d '{"externalId": "test-005", "amount": 10000, ...}'
Expected: Both requests return the same payment. No duplicate charge.

Integration Testing Checklist

Test these scenarios before going live:
  • ✅ Successful payment with Visa
  • ✅ Successful payment with Mastercard
  • ✅ Successful payment with Amex
  • ✅ Declined payment handling
  • ✅ Expired card error
  • ✅ Invalid CVC error
  • ✅ Trigger 3DS challenge
  • ✅ Display authentication iframe
  • ✅ Handle successful authentication
  • ✅ Handle failed authentication
  • ✅ Handle authentication timeout
  • ✅ Save card during payment
  • ✅ Charge saved card
  • ✅ Handle expired saved card
  • ✅ Update saved card
  • ✅ Full refund
  • ✅ Partial refund
  • ✅ Multiple partial refunds
  • ✅ Handle refund errors
  • ✅ Network errors
  • ✅ Validation errors
  • ✅ Declined payments
  • ✅ Rate limiting
  • ✅ Retry logic

Performance Testing

Test your integration under load:
// Example: Load test with 100 concurrent payments
const promises = [];
for (let i = 0; i < 100; i++) {
  promises.push(
    createPayment({
      externalId: `load-test-${i}`,
      amount: 10000,
      currency: 'MXN',
      // ...
    })
  );
}

const results = await Promise.allSettled(promises);
const successful = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;

console.log(`Successful: ${successful}, Failed: ${failed}`);

Go Live Checklist

Ready to switch to production?
1

Get Production Credentials

Contact support@cheqpay.com for live API keys.
2

Update Base URL

Change from sandbox.cheqpay.com to api.cheqpay.com.
3

Register Production Webhooks

Provide your production webhook URL to support.
4

Secure Your Keys

Use environment variables, never hardcode keys.
5

Set Up Monitoring

Track payments, errors, and webhook delivery.
6

Test in Production

Process a small test transaction to verify everything works.

Going Live Guide

Complete production readiness checklist

Need Help?

API Reference

View complete API documentation

Error Handling

Learn how to handle errors

Best Practices

Follow recommended patterns

Contact Support

Get help from our team