Test mode and the sandbox
Use your sk_test_ key against the sandbox base URL:
https://europe-west1-pamopay-fintech.cloudfunctions.net/merchantSandboxApiThe request and response shapes are the live ones, plus two fields on every charge: livemode: false, and a simulation note saying which branch you asked for. Every response also carries a PamoPay-Mode: test header, so curl -i at two in the morning says which environment answered without parsing JSON.
The sandbox is a separate deployment that cannot reach the ledger — it runs under a database role that holds no permission to write money. Test money can never become real money by mistake, and the sandbox balance is always zero because there is nothing to hold.
Routes
POST /v1/charges, GET /v1/charges/{id}, GET /v1/charges, GET /v1/balance. Bills, payment links, POS charges and the event log are live-only today. Test events are delivered to the endpoint registered for test mode and carry livemode: false.
The payer is a table
The outcome is chosen by the msisdn you send. All five are in +255 700 000 00x, an unallocated prefix no real handset can hold.
msisdn | Outcome | Tests |
|---|---|---|
+255700000001 | succeeded after a few seconds | the happy path |
+255700000002 | failed — PAYER_DECLINED | the payer dismissed the prompt |
+255700000003 | failed — INSUFFICIENT_FUNDS | no float |
+255700000004 | never resolves — stays processing | your own timeout and expiry handling |
+255700000005 | succeeded after 90 seconds | a payment that lands after your page gave up |
Any other valid Tanzanian number is approved. Test the last two. They are the branches you cannot arrange against a real network, and the ones most integrations ship wrong: an order page that spins for ever, and an order marked failed that was then paid.
The sandbox refuses what production refuses
POST /v1/chargesneeds anIdempotency-Key. The same key with the same request returns the same charge; a different amount under the same key isIDEMPOTENT_MISMATCH. Test your retry path here.- Scopes are honoured. A read-only test key gets
FORBIDDEN_SCOPEonPOST /v1/chargesexactly as live would. - Amounts, rails and currency are validated identically. Fees are priced from your real tariff, so the
fee_minoryou see is the one you will be charged. - A test key is limited to 120 requests a minute.
What is different
- The balance is always zero, and says so in a
note. - A test key has no "last used" timestamp on the dashboard, by construction: the sandbox cannot write to the table that holds it.
- A test charge is swept after thirty days.
Going live changes two strings
The base URL and the key. Nothing else. If your integration reads both from configuration, going live is a config change and a deploy.