A simulated chain, including the three failures nobody can otherwise rehearse.
A sk_test_ key puts every call against a simulated chain. Same endpoints, same response shapes, same events — the only difference is that nothing costs anything and the chain does what you tell it.
Because the code that goes to production is then the code you tested. A separate sandbox host means a base URL somebody has to remember to change, and the class of bug where they did not.
Test data and live data are separate; a test key cannot see live objects, and a live key cannot see test ones.
curl -X POST $PAYGATE/v1/test/payments/{id}/pay \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" -d '{}'
Pay a partial amount to reach underpaid:
curl -X POST $PAYGATE/v1/test/payments/{id}/pay \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-d '{"amount_decimal":"10.00"}'
Confirmations arrive when blocks do. Advance them:
curl -X POST $PAYGATE/v1/test/advance \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-d '{"blocks":12}'
Reorg. Undo a confirmed payment. paid becomes reversed, and payment.reversed is emitted:
curl -X POST $PAYGATE/v1/test/payments/{id}/reorg \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" -d '{}'
Late payment. Let a payment expire, then pay it. It completes rather than being lost, and raises payment.late_payment.
Underpayment. Above. Then top it up with a second partial payment and watch it complete.
If your integration has never seen these three, it has not been tested — it has been demonstrated.
Recurring payments need a customer's on-chain authorisation, which in test mode is a call rather than a wallet signature:
# fund the simulated customer
curl -X POST $PAYGATE/v1/test/mandates/{id}/fund \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-d '{"amount_decimal":"500.00"}'
# and sign
curl -X POST $PAYGATE/v1/test/mandates/{id}/authorize \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" -d '{}'
A simulated authorisation raises mandate.authorized exactly as a real one does — otherwise test mode would authorise the mandate and tell nobody, and that event is the only report you have that your customer signed.
It is not a second gateway. /v1/test/* answers 501 on a deployment built without the simulated chain — which is honest, where a 404 would suggest the endpoint was never real.