Skip to content

Test mode and the sandbox

Use your sk_test_ key against the sandbox base URL:

https://europe-west1-pamopay-fintech.cloudfunctions.net/merchantSandboxApi

The 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.

msisdnOutcomeTests
+255700000001succeeded after a few secondsthe happy path
+255700000002failedPAYER_DECLINEDthe payer dismissed the prompt
+255700000003failedINSUFFICIENT_FUNDSno float
+255700000004never resolves — stays processingyour own timeout and expiry handling
+255700000005succeeded after 90 secondsa 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/charges needs an Idempotency-Key. The same key with the same request returns the same charge; a different amount under the same key is IDEMPOTENT_MISMATCH. Test your retry path here.
  • Scopes are honoured. A read-only test key gets FORBIDDEN_SCOPE on POST /v1/charges exactly as live would.
  • Amounts, rails and currency are validated identically. Fees are priced from your real tariff, so the fee_minor you 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.

Every code, scope and route on this site is rendered from the API's own source.