GuidesTest with the sandbox

Test with the sandbox

Soxara’s sandbox lives at the same URL as production. The prefix on your API key — sxm_test_* vs sxm_live_* — routes you into one or the other. See Environments for the full mental model; this page is the practical “how do I actually generate a test payment.”

The one real lever: simulate

There’s no matrix of magic test card numbers or MSISDNs today. Instead, a test-key payment link can be settled directly through POST /v1/api/payment-links/{id}/simulate — it runs a test link only (a live link 404s) through the exact same settlement fan-out production uses, so you get a real payment.completed/payment.failed webhook without touching a card network or MoMo at all:

# 1. Create a test link
curl -X POST "$SOXARA_BASE/v1/api/payment-links" \
  -H "Authorization: Bearer $SOXARA_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Test order", "amount": 1250, "currency": "USD"}'
# → { "data": { "id": "…", "link_code": "PL-TESTXXXX", ... } }
 
# 2. Settle it however you want to exercise
curl -X POST "$SOXARA_BASE/v1/api/payment-links/{id}/simulate" \
  -H "Authorization: Bearer $SOXARA_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{"method": "momo", "outcome": "completed"}'

method is card, momo, or wallet; outcome is completed or failed. For a pay-any-amount link, pass amount (minor units) too. This is the path to exercise both the happy path and the failure path in CI without any hosted-checkout browser step.

Recurring billing (mandates)

Test-key mandates activate immediately — POST /v1/api/mandates with a test key skips the customer approval step (card save / wallet PIN) that a live mandate requires, so you can start charging right away. On a test mandate, POST /v1/api/charges also takes an "outcome": "completed"|"failed" field to force the result, the same idea as simulate. See Recurring billing.

Sub-merchants and payroll

Sub-merchant provisioning (/v1/api/sub-merchants/*) and payroll (/v1/api/payroll/*) run their normal flow in test mode too — the OTP round-trip and the PIN-gated approval both still happen, just against test accounts/wallets rather than real ones. There’s no shortcut that skips either step, in test mode or live.

Gift cards and fleet cards

Redemption (/v1/gift-cards/redeem, /v1/fleet-cards/redeem) needs a real test gift card or fleet card issued against your test merchant first (from the dashboard) — there’s no synthetic “any id redeems successfully” shortcut. Use the fleet-card error codes (FLEET_CARD_INACTIVE/CATEGORY_NOT_ALLOWED/INSUFFICIENT_BALANCE/SPEND_LIMIT_EXCEEDED) to exercise your handling of each rejection by setting up a test card in the matching state.

Rate limits

Sandbox and live traffic share the same rate-limiting middleware and budgets — there’s no separate, more generous sandbox limit. If your test suite hits 429 RATE_LIMITED running back-to-back, add backoff in the suite itself rather than assuming it won’t happen live.

Webhooks in sandbox

Register a separate webhook endpoint for your test key — it gets its own signing secret, independent of your live endpoint’s. Don’t reuse a secret across environments; verification will fail. See Testing webhooks for tunneling a local handler and redelivering a failed test delivery.

Test-mode payment payloads carry "environment": "test" inside data.payment — check that instead of a top-level livemode field, which doesn’t exist in the payload.

What sandbox doesn’t simulate

  • Real settlement timing. simulate resolves instantly; a live card or MoMo payment settles on its own schedule.
  • Real fraud/KYC screening. Test accounts and test payment methods don’t go through the checks a live customer or merchant does.
  • Real network failures. Sandbox is more reliable than production. Don’t assume your retry logic is sound just because sandbox tests pass — the real value of simulate’s "outcome": "failed" is exercising your failure-handling code path deliberately, not standing in for chaos testing.