easyway
Create account

Test crypto payments in sandbox mode

Before a single real coin moves, you can run your whole integration against the sandbox: create invoices, receive paid, underpaid and expired webhooks, and make payouts. Nothing touches a blockchain. This guide shows what the sandbox does, what it does not do, and a test plan that covers the cases real customers will hit.

How the sandbox works

Every easyway account exists twice, test and live, with separate API keys, webhook endpoints, invoices and balances. The API key decides the mode: a sk_test_ key creates test data, a sk_live_ key real data. In the dashboard, the Test mode switch in the top bar shows the sandbox side.

In test modeWhat happens
InvoicesSame request, same response, same hosted payment page with a "Test mode, do not send real funds" banner. Addresses look real, but nobody controls them.
PaymentsSimulated from the dashboard with Simulate payment. The deposit is recorded as fully confirmed and goes through the real crediting code, fee and webhooks.
RatesLive prices when they are available, otherwise a fixed fallback price, so a test never fails because a price source is down. Live invoices never use the fallback.
PayoutsNo whitelist needed. A payout is settled at once with a fake tx_hash starting with test_, and payout.sent is sent.
WebhooksTheir own list of endpoints with their own signing secrets. Send test delivers a webhook.test event.

1. Set up the test side

Switch the dashboard to test mode, then in Developers create a test API key (scope invoices:write, plus invoices:read and payouts:write if you want to try those) and add a test webhook endpoint. Press Send test on the endpoint first: if your handler answers 2xx to the webhook.test event, the URL is reachable and your signature check works. The webhook signatures guide has handlers and a local curl test.

The webhook URL must be a public https:// address, in test mode too: localhost, private networks and plain HTTP are refused. During development, expose your local server with an HTTPS tunnel and use its public URL.

2. Create a test invoice from your code

The call is the same as in production. Only the key changes, so keep it in configuration, not in code.

Node.js

// Same call as in production, only the key is a sk_test_ key
const res = await fetch("https://api.easyway.cash/v1/invoices", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.EASYWAY_API_KEY}`,
    "Idempotency-Key": "test-order-1",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "amount": "25.00",
    "currency": "USD",
    "allowed_assets": [
      "USDT_TRC20"
    ],
    "order_id": "test-order-1"
  }),
});
if (!res.ok) throw new Error(`easyway ${res.status}: ${(await res.json()).error}`);
const data = await res.json();

PHP

// Same call as in production, only the key is a sk_test_ key
$ch = curl_init("https://api.easyway.cash/v1/invoices");
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode([
    "amount" => "25.00",
    "currency" => "USD",
    "allowed_assets" => ["USDT_TRC20"],
    "order_id" => "test-order-1",
  ]),
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("EASYWAY_API_KEY"),
    "Idempotency-Key: test-order-1",
    "Content-Type: application/json",
  ],
]);
$data = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) {
  throw new Exception("easyway: " . $data["error"]);
}

Python

# Same call as in production, only the key is a sk_test_ key
import os, requests

res = requests.post(
    "https://api.easyway.cash/v1/invoices",
    headers={
        "Authorization": f"Bearer {os.environ['EASYWAY_API_KEY']}",
        "Idempotency-Key": "test-order-1",
    },
    json={
        "amount": "25.00",
        "currency": "USD",
        "allowed_assets": ["USDT_TRC20"],
        "order_id": "test-order-1",
    },
)
res.raise_for_status()
data = res.json()

Open the returned payment_url to see the payment page your customers will get. With allowed_assets limited to one coin the address is assigned straight away; otherwise pick a coin on the page.

3. Simulate payments

In the dashboard (test mode), open the invoice under Payments. Simulate payment pays the amount that is still due. If no coin was chosen yet, it uses the first coin in allowed_assets, or USDT on TRON. Once a coin is chosen, an amount field next to the button lets you send less. Simulated payments are always fully confirmed, so you get invoice.paid or invoice.underpaid directly, not invoice.confirming.

Case to testHowWhat your code should do
Full paymentPress Simulate payment with the field empty.invoice.paid: fulfil the order exactly once.
UnderpaymentEnter less than the amount due, then press.invoice.underpaid with remaining_amount: do not fulfil yet.
Top-up after an underpaymentPress again with the field empty.invoice.paid for the same invoice: now fulfil.
ExpiryCreate an invoice with lifetime: 5 (minutes, the minimum) and wait.invoice.expired: release the stock, keep the order.
Late paymentSimulate a payment on that expired invoice (its coin must already be chosen).invoice.paid after invoice.expired: your handler must still accept it.
Duplicate deliveryMake your endpoint answer 500 once; the retry comes about a minute later.The retry carries the same event id: ignore it the second time.

The webhook log in the dashboard lists every delivery with its HTTP status, so you can see what your handler answered. Your test balance grows by each simulated payment minus the fee, the same way a live balance does.

4. Try a payout

With payouts:write on the test key, a payout from your test balance needs no whitelisted address and is settled immediately. The address must still be valid for the coin, and the amount must be at least the coin's minimum payout.

// Sandbox: no whitelist needed, settled at once with a test_ tx hash
const res = await fetch("https://api.easyway.cash/v1/payouts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.EASYWAY_API_KEY}`,
    "Idempotency-Key": "test-payout-1",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "asset": "USDT_TRC20",
    "address": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
    "amount": "10",
    "note": "Sandbox payout"
  }),
});
if (!res.ok) throw new Error(`easyway ${res.status}: ${(await res.json()).error}`);
const data = await res.json();

What the sandbox cannot show you

Not coveredWhy it matters in live mode
Real confirmation timesA real payment first sends invoice.confirming and becomes paid after the coin's confirmations, from seconds to tens of minutes.
Wallet behaviourSome wallets deduct the network fee from the amount sent, which makes an invoice underpaid. Set an underpayment tolerance in Settings.
Whitelist and 2FALive payouts go only to whitelisted addresses, and live money actions, including a live key that can send payouts, need 2FA.

The best last test is a real payment of a few dollars with a sk_live_ key. The go-live checklist lists the rest, and the USDT TRC-20 guide shows a complete integration from invoice to fulfilment.

Create a merchant accountRead the API docs
Test crypto payments in sandbox mode | easyway