Sandbox Environment

Everything here is fake money. Same endpoints as production, prefixed with /sandbox, using your ck_test_... API key. No STK push reaches a real phone — you resolve payments yourself with the simulator.

Read the Sandbox Guide Create an Account

Base URL

https://pay.cloud.or.ke/sandbox/api

Every live route exists here: /sandbox/api/auth/register, /sandbox/api/payments/mpesa/stkpush, /sandbox/admin/merchants … OTP codes are returned in the JSON (sandboxOtp) so you can automate tests.

1 · Get an account access token, then push STK

Each M-Pesa account has its own sandbox consumer key + secret (dashboard ▸ M-Pesa Accounts). Exchange them for a token — no accountId needed.

# a) token
curl -X POST https://pay.cloud.or.ke/sandbox/api/oauth/token \
  -u "SANDBOX_CONSUMER_KEY:SANDBOX_CONSUMER_SECRET"

# b) STK push with the returned Bearer token
curl -X POST https://pay.cloud.or.ke/sandbox/api/payments/mpesa/stkpush \
  -H "Authorization: Bearer <access_token>" -H "Content-Type: application/json" \
  -d '{"phone":"254712345678","amount":150}'

2 · Resolve it with the simulator

resultCode 0 = paid, 1032 = cancelled by user, 1 = insufficient funds, 1037 = timeout, 2001 = wrong PIN.

curl -X POST https://pay.cloud.or.ke/sandbox/api/sandbox/simulate/stk-result \
  -H "X-API-Key: ck_test_..." -H "Content-Type: application/json" \
  -d '{"reference":"TXN-XXXXXXXXXX","resultCode":0}'

3 · Fund the sandbox Airtel wallet

curl -X POST https://pay.cloud.or.ke/sandbox/api/sandbox/wallet/topup \
  -H "X-API-Key: ck_test_..." -H "Content-Type: application/json" \
  -d '{"amount":10000}'

Fraud rule testing

Simulate 21 failed/cancelled results (1032 or 1) against one M-Pesa account and watch it get suspended as fraud — while a second linked account keeps transacting. This mirrors production behaviour exactly.