Sandbox Overview
Sandbox mode lets you exercise the entire integration — create, checkout, callback, webhook, verify — without touching a blockchain. A sandbox payment is a real payment row in every respect except that no deposit address is allocated and no chain is watched; instead, the checkout page shows simulation buttons that drive the payment into the outcome you choose.
Creating a sandbox payment
Add "isSandbox": true to an ordinary create call:
curl -X POST https://api-staging.finomesh.com/api/v1/payments \
-H "X-Api-Key: $FINOMESH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"currencyCode": "USD",
"amount": "49.99",
"callbackUrl": "https://shop.example.com/payment/result",
"webhookUrl": "https://shop.example.com/api/finomesh-webhook",
"isSandbox": true
}'
Your real API key is still required — sandbox changes the post-create lifecycle, not authentication. The create response is the usual { paymentUid, paymentUrl }; the sandbox nature shows up on the hosted checkout, where each offer carries a sentinel deposit address (SANDBOX-DEPOSIT-ADDRESS-DO-NOT-USE) that is invalid on every chain by construction.
Driving the outcome
Open the checkout (https://checkout-staging.finomesh.com/p/{paymentId}) — instead of an address and QR code you get four buttons:
| Button | Resulting status |
|---|---|
| Simulate success | SUCCESS |
| Simulate acceptable | ACCEPTABLE |
| Simulate mismatch | MISMATCH |
| Simulate expiry | EXPIRED |
Each button drives the payment into that terminal state, and everything downstream fires exactly like production: the result screen, the callback redirect (for the three paid outcomes), the signed webhook (all four outcomes), and the verify response.
You can also drive the outcome programmatically — the buttons call a public endpoint, so an end-to-end test suite can too:
curl -X POST https://api-staging.finomesh.com/api/v1/public/payments/{paymentId}/sandbox/simulate \
-H "Content-Type: application/json" \
-d '{ "outcome": "success", "offerId": "{offerId}" }'
outcome ∈ success | acceptable | mismatch | expire. offerId (one of the payment's offers, as served to the checkout by GET /api/v1/public/payments/{paymentId}) lets the simulator synthesize a realistic received amount for the paid outcomes; it is optional and unused for expire. Simulating a non-sandbox payment is rejected — a live payment cannot be driven this way.
What to test before going live
- Callback handler: reads
paymentId, ignoresstatusfor fulfilment, verifies server-side. - Webhook handler: signature verification against the raw body, fast
200, dedupe bypaymentId. - All four outcomes:
SUCCESSandACCEPTABLEfulfil,MISMATCHholds for review,EXPIREDcancels. - Webhook retry: respond
500once and confirm the redelivery ~15s later succeeds. - Expiry without webhook delivery: confirm your reconciliation (poll-verify or manual) catches it.
Run sandbox tests against staging (api-staging.finomesh.com). Staging runs the same code as production but settles on test networks, so even non-sandbox rehearsals are safe there.