Guide
Sandbox testing
What the sandbox simulates, and how to drive each outcome deliberately.
The sandbox at https://api-sandbox.mojaswitch.com/v1 runs the same API, the
same validation and the same durable payment path as production. It moves no
real money, and it uses sk_sandbox_ keys only.
What differs from production
| Sandbox | Production | |
|---|---|---|
| Keys | sk_sandbox_…, from POST /signup or Moja Switch |
sk_live_…, issued at go-live |
FX without fx (BYO-FX tenants) |
Priced from a sandbox rate on corridors that have one (CA-TZ) | Refused: 422 fx_required |
Collections (kind: collection) |
Available after product activation | 403 product_not_available |
| Tenant payouts | Simulated; the outcome is yours to choose | Not offered yet |
| Outcome controls | sandbox.collection_outcome, PUT /sandbox/payout-outcome, webhook redelivery |
None |
A sandbox rate is a development convenience. Never use it to estimate production prices.
Collection outcomes
Set sandbox.collection_outcome when you create a collection
(see Collections):
| Value | What happens |
|---|---|
paid |
The payer pays the full amount. The collection is collected and becomes eligible for a tenant payout. |
expired |
The payer never pays. The collection ends FAILED with failure.code: collection_not_completed. |
amount_mismatch |
The payer pays a different amount. The collection is held at COLLECTING for review by Moja Switch operations. |
reverse_after_collect |
The payer pays, then the payment is reversed. The collection is never paid out. |
Payout outcomes
PUT /sandbox/payout-outcome sets the outcome of your next tenant payout.
The value is captured when the payout is created.
curl -sS -X PUT "$MOJA_BASE/sandbox/payout-outcome" \
-H "Authorization: Bearer $MOJA_KEY" \
-H 'Content-Type: application/json' \
-d '{ "payout_outcome": "returned" }'| Value | Payout status path |
|---|---|
paid |
CREATED → SENT → PAID |
delayed |
CREATED → SENT, staying SENT for longer before PAID |
failed_before_send |
CREATED → FAILED. Its collections become eligible for a later payout. |
returned |
CREATED → SENT → RETURNED. Its collections become eligible for a later payout. |
Webhook redelivery
POST /sandbox/webhooks/{event_id}/redeliver sends an event you have already
received again: same event_id, same body, fresh signature. Use it to prove your
receiver deduplicates. It moves no money and changes no state.
A checklist before you ask to go live
- Every money call persists its
Idempotency-Keybefore the first attempt, and retries reuse it. - Your webhook receiver verifies signatures on raw bytes, rejects stale timestamps and deduplicates on event id.
- A reconciliation job reads
GET /payments/{payment_id}for anything not terminal in your expected window. - You branch on
error.codeand handle201 FAILEDpayments. - Amounts use each currency's
minor_unit_exponentfromGET /corridors/{corridor_code}. - No API key or webhook secret appears in logs, URLs, client code or tickets.
