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 “what works in sandbox.”

Cards

Use Stripe’s standard test card numbers. They behave the same way they do in Stripe Test mode.

NumberBehavior
4242 4242 4242 4242Succeeds immediately
4000 0000 0000 0002Declined: generic_decline
4000 0000 0000 9995Declined: insufficient funds
4000 0000 0000 0069Declined: expired card
4000 0027 6000 3184Requires 3DS authentication
4000 0000 0000 0341Charge succeeds, then later disputed by the cardholder

For each, use any future expiry, any CVC, any postal code.

MTN Mobile Money (Liberia)

MSISDN patternBehavior
Any well-formed +231XX1234567Succeeds after a 5-second synthetic delay
+231881111000Always succeeds
+231881111001Always fails (user_rejected — simulates a customer hitting cancel)
+231881111002Times out (simulates a phone that never responds)
+231881111003Returns INSUFFICIENT_FUNDS

No real PIN prompt goes to any phone in sandbox.

Orange Money (Liberia)

Same pattern as MTN, replace 881 with 771:

MSISDNBehavior
+231771111000Succeeds
+231771111001Fails (user_rejected)
+231771111002Times out
+231771111003INSUFFICIENT_FUNDS

Soxara wallet

You can’t debit a real user’s wallet from sandbox — that’s by design. Test-mode merchant keys are blocked from payment_method: soxara_wallet to prevent accidental cross-environment debits.

To test the wallet flow without touching a real user:

  • Create a test buyer through the merchant dashboard (sandbox section). They get a synthetic Soxara wallet pre-funded with $1000 fake USD.
  • Use their phone number with soxara_wallet payment method.
  • The wallet drains and the payment succeeds.

Viral transfers

Send to any phone number that’s not an existing Soxara user. The recipient never gets a real WhatsApp message; the test environment auto-completes the claim after 10 seconds. You’ll get a transfer.claimed webhook.

Bill payments

Sandbox biller responses:

BillerTest pattern
LEC (electricity)Any account number; success returns a 16-digit test token
LWSC (water)Any account number; succeeds
MTN airtimeAny MSISDN; succeeds
Orange airtimeAny MSISDN; succeeds
account_number: 00000000 (any biller)Returns BILLER_DECLINED
account_number: 99999999 (any biller)Times out

Refunds + disputes

  • Refunds against a succeeded test payment work instantly.
  • To simulate a dispute against a card payment: use card 4000 0000 0000 0341. The payment succeeds; a charge.dispute.created webhook fires ~30 seconds later.

Rate limits

Test mode has generous rate limits — enough that you can run a full integration test suite repeatedly without hitting them.

Live mode rate limits are lower and per-endpoint-group. Don’t tune your retries against sandbox limits.

Webhooks in sandbox

Test webhook endpoints have separate signing secrets from live endpoints. Don’t reuse the secret across environments — the signature check will fail.

Test-mode webhook payloads have "livemode": false. Use this as a secondary check in your handler:

if (event.livemode !== expectingLiveMode) {
  // Misrouted event — something's wrong with your env setup
  console.error('webhook env mismatch', event.id);
  return res.status(200).send('ignored');
}

What sandbox doesn’t simulate

  • Real settlement timing. Test payments settle instantly; live cards settle T+1 or T+2 depending on the issuer.
  • Real fraud signals. Test cards always behave the same way; live cards trigger Stripe’s risk engine.
  • Real disputes from real cardholders. Test disputes are scripted; live disputes happen on their own schedule (often weeks after the original charge).
  • Real network failures. Sandbox is more reliable than production. Don’t assume your retry logic is sound just because sandbox tests pass — add chaos tests in staging before going live.