POST /v1/payments can specify a scenario field that deterministically controls the simulation outcome: final status, latency, and error message. This is the primary lever for testing your failure paths.
Reference table
Latency is drawn randomly within the stated range on every call — reproducing the natural variance of a real operator.
Without a scenario: the SIM registry (test clients)
If you omit thescenario field, SandPay behaves like a real operator and uses your test clients (/clients in the dashboard) as a SIM registry:
Configurable per environment: the
unknownMsisdnPolicy is reject by default (unknown number → UNKNOWN_MSISDN). In passthrough mode, an unknown number is forwarded to the operator adapter (legacy behaviour). The blocked and insufficient-funds cases always remain final.Example — triggering pin_invalid
Statuses and finality
The 11 canonical statuses are stable — yourswitch can enumerate them. PENDING is the only non-final status: it indicates a transaction is currently being processed (useful for async flows). All other statuses are final and trigger the payment.completed webhook.
Out-of-scenario: description
The description field (optional) is purely informational — it is stored on the transaction and returned in the webhook payload, but does not influence the outcome.
Raw operator response (raw)
Every payment resource (POST /v1/payments, GET /v1/payments/{id}, items in GET /v1/payments, and the payment.completed webhook) contains a raw field with the native operator shape corresponding to the scenario played.
This shape is synthesised with _simulated: true at the top level — it faithfully reproduces the native structure returned by each operator.
MTN example — scenario: "success":
scenario: "low_balance":
scenario: "success" (note: the SUCCESSFULL typo is faithfully reproduced from the real API):
See also
- Quickstart — first API call
- Webhooks — receive the final status via push
- Errors — HTTP codes and retry strategy