Test mode

Test mode is a full copy of payitgo with no coins: the same API, the same webhooks and the same checkout. Build your whole integration with your test key, then go live by swapping in the live key and the live webhook secret. The dashboard shows each key with a Test or Live badge (its livemode): the key's text does not say.

A test payment gets an address, an amount and a deadline like a live one, but nothing watches a blockchain for it. You choose what happens to it instead: simulate an outcome, and payitgo plays the deposits, status changes and webhooks a few seconds apart, exactly as live coins would. You never need test coins, a faucet or a wallet.

What is separate

Test key Live key
Payments Test payments only; "livemode": false Live payments only; "livemode": true
Order ids A test order_id never collides with a live one
Coins None: you simulate what the customer sends Real coins on mainnet
Webhook The test webhook: its own URL and its own secret The live webhook: its own URL and secret
Money Your test balance, never paid out; direct settlement to a test payout address completes on paper Your balance, or your live payout addresses after their 24-hour wait
Dashboard Add ?mode=test to dashboard routes The default

A test key reading a live payment gets 404, and the other way round.

Simulate a payment

There are three ways to choose an outcome:

The payment shows its simulation, and so does every webhook it sends:

{
  "id": "9e3a858c7701308142d545f9",
  "order_id": "ORD-2001",
  "status": "pending",
  "token": "USDT_TRC20",
  "deposit_address": "TPk2bpQqWRpXiaUHS9SXnZCDgvwrsuJ71o",
  "livemode": false,
  "simulation": { "outcome": "confirmed", "state": "running" },
  "…": "…"
}

state is requested (asked for on create, waiting for the coin), running or done.

Outcomes

outcome What happens Webhooks, in order
confirmed The exact amount arrives and confirms payment.detecting, payment.confirmed
overpaid 5% more than due arrives payment.detecting, payment.confirmed with "overpaid": true
partially_paid Half of what is still due arrives. The payment stays open: simulate again to pay the rest, or let it run out payment.partially_paid
underpaid Half arrives, then the deadline passes payment.partially_paid, payment.underpaid
expired Nothing arrives, and the deadline passes now payment.expired (payment.underpaid if it was partially paid)
failed The amount arrives, then its transaction is dropped payment.detecting, payment.failed, its deposit's fail_reason saying why
wrong_asset The amount arrives in another coin on the same network (TRX for USDT on Tron, USDC for USDT on BSC…) payment.wrong_asset. With accept_other_assets on: payment.detecting, payment.wrong_asset, payment.confirmed
paid_late Half arrives, the deadline passes, then the rest arrives payment.partially_paid, payment.underpaid, payment.detecting, payment.late_deposit, payment.confirmed with "paid_late": true

Each step comes about 5 seconds after the one before, and its webhook is posted within about 5 seconds of it, so a whole simulation is over in less than half a minute. Two events of the same step (payment.detecting and payment.late_deposit, say) can reach you in either order, as live ones can.

The rules:

Status error Cause
400 outcome is required No outcome in the body
400 simulate must be one of: confirmed, overpaid, … An unknown outcome
400 only test payments can be simulated: use a test key A live payment, or simulate with a live key
400 choose the payment's coin first No coin yet, for anything but expired
400 this payment is confirmed: only a pending or partially paid payment can be simulated The payment is closed or confirming
400 a BTC address only receives BTC: wrong_asset needs a coin on Tron, Ethereum or BNB Smart Chain wrong_asset on a coin that has no other
400 simulate wrong_asset needs the payment's coin: pass token wrong_asset on create without token
404 payment not found Another merchant's payment, or another mode's
409 a simulation is already running for this payment Wait for its done

What a simulated payment looks like

A simulated deposit is like a live one, with "simulated": true. Its tx_id looks like a transaction hash (64 hex characters) but is on no chain, so it has no explorer_url. Tell a simulated deposit by its simulated, never by its tx_id:

"deposits": [
  { "tx_id": "f916492ece53d1cb5bab9515cfed47d17648649cbd8e52b5a45f6e4da6e6fdf9", "token": "USDT_TRC20", "chain": "tron",
    "amount": "20", "usd_value": "20", "counted": true, "status": "confirmed", "simulated": true, "…": "…" }
]

Everything after it works as live:

Never send real coins to a test address

Nothing watches a test payment's address, so coins sent to it are never seen. Test addresses on Tron, Ethereum, BNB Smart Chain, Dogecoin and XRP look exactly like live ones, and a wallet will pay them: the coins would stay on the address until payitgo's support recovers them. Bitcoin and Litecoin test addresses use the testnets' formats, which real wallets refuse.

Scenarios to test

Create a test payment for each, and check what your server receives.

Scenario How What you should see
Normal payment confirmed payment.detecting, then payment.confirmed
Partial, then completed partially_paid, then confirmed once it is done payment.partially_paid, then payment.detecting, payment.confirmed
Underpaid underpaid payment.partially_paid, then payment.underpaid
Expired expired, or create a payment and simulate nothing payment.expired (15 minutes later without a simulation, an hour if no coin was chosen)
Overpaid overpaid payment.confirmed with "overpaid": true
Late completion paid_late payment.underpaid, then payment.late_deposit and payment.detecting, then payment.confirmed with "paid_late": true
Dropped transaction failed payment.detecting, then payment.failed
Wrong asset wrong_asset payment.wrong_asset; the deposit counts only with accept_other_assets on
XRP Create with "token": "XRP" and simulate The payment has its destination_tag; show it as prominently as the address
Customer chooses the coin Create without token, with simulate; choose on the checkout page token_options, then an address after the choice, then the outcome's webhooks
Retry safety Create the same order_id twice The second call returns the first payment with 200
Per-payment URL Create a payment with webhook_url and simulate Its notifications go to that URL, not the account's

Test your webhook handler too:

Scenario How What you should see
Signature check POST /v1/webhook/test, and verify the ping Your handler answers 200
Tampering Change one byte of a captured body and send it to your handler Your handler answers 400
Retries Answer 500 once The same notification again a minute later, with X-Webhook-Attempt: 2
Duplicates Redeliver a delivery from the dashboard Your handler recognises the event id and does nothing twice
Test vs live Look at livemode Your production handler ignores "livemode": false

Going live

  1. The account owner turns live mode on: POST /merchant/live {"enabled": true} (Account and team).
  2. With the live key, read the live webhook secret (GET /v1/webhook) and set the live URL (PUT /v1/webhook).
  3. Put the live key and the live webhook secret on your production server. Remove any simulate from your payment requests: a live key refuses it.
  4. Check that your handler fulfils only "livemode": true events in production.
  5. Add a live payout address for each coin you accept (payout addresses). A new live address is used after 24 hours.
  6. Make one small real payment end to end.