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.
| Number | Behavior |
|---|---|
4242 4242 4242 4242 | Succeeds immediately |
4000 0000 0000 0002 | Declined: generic_decline |
4000 0000 0000 9995 | Declined: insufficient funds |
4000 0000 0000 0069 | Declined: expired card |
4000 0027 6000 3184 | Requires 3DS authentication |
4000 0000 0000 0341 | Charge succeeds, then later disputed by the cardholder |
For each, use any future expiry, any CVC, any postal code.
MTN Mobile Money (Liberia)
| MSISDN pattern | Behavior |
|---|---|
Any well-formed +231XX1234567 | Succeeds after a 5-second synthetic delay |
+231881111000 | Always succeeds |
+231881111001 | Always fails (user_rejected — simulates a customer hitting cancel) |
+231881111002 | Times out (simulates a phone that never responds) |
+231881111003 | Returns INSUFFICIENT_FUNDS |
No real PIN prompt goes to any phone in sandbox.
Orange Money (Liberia)
Same pattern as MTN, replace 881 with 771:
| MSISDN | Behavior |
|---|---|
+231771111000 | Succeeds |
+231771111001 | Fails (user_rejected) |
+231771111002 | Times out |
+231771111003 | INSUFFICIENT_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_walletpayment 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:
| Biller | Test pattern |
|---|---|
| LEC (electricity) | Any account number; success returns a 16-digit test token |
| LWSC (water) | Any account number; succeeds |
| MTN airtime | Any MSISDN; succeeds |
| Orange airtime | Any 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; acharge.dispute.createdwebhook 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.