Test mode
Force every outcome with a test key — no faucet, no wallet, no testnet. Including the outcomes that break consumers.
Any consumer handles confirmed. The ones that break in production break on underpaid and on a
redelivery. A test key reaches both in one HTTP call.
curl -X POST "$CPAY_API_URL/v1/test/payments/$PAYMENT_ID/simulate_payment" \
-H "authorization: Bearer $CPAY_SECRET_KEY" \
-H "content-type: application/json" \
-d '{"outcome":"underpaid","chain":"chromia"}'Or through the SDK:
await cpay.testMode.simulatePayment(payment.id, { outcome: "underpaid", chain: "chromia" })outcome—confirmed,partial,overpaid,underpaidorexpired. Defaultconfirmed.partialpays some of the total and leaves the payment open, which is the only way to reach the top-up path:underpaidis a verdict reached at expiry, so it lands on a payment with nothing left to top up.confirmedsettles whatever is still outstanding, sopartialthenconfirmedis a part payment followed by the rest — and confirms rather than reading as an overpayment.chain—chromiaorbsc. Defaultchromia. The chain has to carry the payment'ssettlement_assetand the environment needs a verified test payout wallet on it, so a stablecoin cannot be simulated on Chromia. Simulation refuses anything the real checkout would refuse.
Simulation is refused outright for live keys.
Paying with a real testnet token
simulate_payment covers every outcome without touching a chain, and that is the right tool for
almost everything. When you do want a real transfer — proving your wallet integration, or checking a
stablecoin's decimals against your own maths — these are the faucets, and they are not
interchangeable:
| Rail | Token | Where to get it |
|---|---|---|
| Chromia testnet | tCHR (native) | faucet.testnet.chromia.com — registers an FT4 account, 1000 tCHR/week |
| BNB testnet | tCHR (BEP-20) | claim() on 0x0e61…aa8d, any EVM address, once a week |
| BNB testnet | USDC / USDT | Binance's BSC testnet faucet. 18 decimals, like their mainnet counterparts |
The two tCHR entries are different tokens on different networks wearing the same ticker. Economy-chain tCHR cannot be spent on the BNB rail, and vice versa.
BSC's stablecoins are 18 decimals while its CHR is 6. Sandbox is the only place to exercise the 18-decimal path before real money touches it, which is why the BSC testnet rail uses genuinely 18-decimal faucet tokens rather than a 6-decimal stand-in.
You will also need gas — tBNB from the BNB testnet faucet.
What to actually check
Running the happy path proves the wiring. These prove the consumer.
Each outcome reaches your consumer, and it returns 2xx
Run all four. Confirm the order ships on confirmed and overpaid, and does not ship on
underpaid or expired.
A mangled signature is rejected
Flip one character of the v1= hex and re-send the delivery. It must 4xx and must not fulfil. If it
fulfils, you are not verifying — and anyone who learns your endpoint URL can hand you free orders.
A replayed delivery fulfils exactly once
Re-send a valid delivery verbatim. The second one should be a no-op.
GET /v1/payments/:id agrees with what you recorded
If the two disagree, trust ours and find out why.
Testing against localhost
A sandbox endpoint may be plain http, for exactly this. If you would rather use a real hostname,
expose the local port:
cloudflared tunnel --url http://localhost:3000Then register the tunnel URL as the endpoint.