Testing and Sandbox Scenarios
Simulate approvals, declines, webhooks and refunds before going live.
Overview
Watenga's sandbox environment is completely isolated from live payments. Sandbox transactions never move real money and do not appear in live reports. Use the same API host as production with test keys — there is no separate sandbox subdomain.
API base URL: https://api.watenga.africa
Authentication: Authorization: Bearer sk_test_… (or rk_test_… for restricted keys)
Create a sandbox API app at: https://developer.watenga.africa/apps → New App → Sandbox
GATEWAY_DRIVER=mock, use documented test PANs or sandbox_scenario on POST /v1/checkout. Full scenario IDs and recipes: GET /v1/developer/sandbox/scenarios or the Developer Console Payment simulator page.Test Cards
| Card number | Brand | What happens |
|---|---|---|
4111 1111 1111 1111 | Visa | Approved: payment.capture.completed |
4012 0000 3333 0026 | Visa | Approved: payment.capture.completed |
5200 0000 0000 0007 | Mastercard | Approved: payment.capture.completed |
4000 0000 0000 0002 | Visa | Declined by the issuer (do not retry): payment.capture.declined |
5200 0000 0000 0114 | Mastercard | Declined by the issuer (do not retry): payment.capture.declined |
4000 0000 0000 0069 | Visa | Declined by the issuer (do not retry): payment.capture.declined |
4000 0000 0000 0127 | Visa | Declined by the issuer (do not retry): payment.capture.declined |
4000 0000 0000 9995 | Visa | Declined, insufficient funds (the buyer may retry) |
4000 0000 0000 0119 | Visa | Provider timeout: stays pending, then fails |
Expiry: any future month, e.g. 12/34. CVC: any 3 digits. Any name and address. Test keys only: these numbers never reach a card network.
Test Mobile Money Numbers
| Number | International | What happens |
|---|---|---|
0771111111 | +263771111111 | Approved at once: payment.capture.completed |
0772222222 | +263772222222 | Pending for about 30 seconds (onPending), then approved |
0773333333 | +263773333333 | The buyer refuses the prompt (onCancel): payment.capture.cancelled |
0774444444 | +263774444444 | Insufficient balance, fails at once: payment.capture.declined |
Any mobile wallet in test mode; no real phone is prompted.
Triggering Specific Error Codes
VALIDATION_ERROR — send amount: 0 or a negative number.
Expected response: 400 { code: 'INVALID_AMOUNT' }
PHONE_REQUIRED — send walletType: 'ecocash' without customerPhone.
Expected: 400 { code: 'PHONE_REQUIRED' }
DUPLICATE_REQUEST — send two checkout requests with the same merchantTransactionId.
Second request returns: 409 { code: 'DUPLICATE_REQUEST' }
RATE_LIMIT_EXCEEDED — send more than 60 requests in 60 seconds.
Expected: 429 { code: 'RATE_LIMIT_EXCEEDED' } — the Retry-After header shows seconds to wait.
KYC_REQUIRED — use a sandbox app whose merchant account has kyc_tier = 0.
Expected: 403 { code: 'KYC_REQUIRED' }
To bypass in sandbox: contact support to set your sandbox merchant to approved KYC.
Testing Webhooks Locally
Watenga webhooks are sent from our servers to the webhook URL set on your app in the developer console. In local development, your server is not publicly accessible. Use one of these tools to expose your local server:
Option 1 — ngrok (recommended)
# Install ngrok from ngrok.com
ngrok http 3000
# ngrok gives you a public URL like:
# https://abc123.ngrok.io
# Set your webhook URL in the Developer Dashboard to:
# https://abc123.ngrok.io/webhooks/watengaOption 2 — Cloudflare Tunnel (free, no account needed)
npx cloudflared tunnel --url http://localhost:3000
# Gives you a permanent-ish URL for the sessionNode.js webhook test script:
// test-webhook.js — simulate a signed webhook event locally
const crypto = require('crypto')
const secret = 'your_webhook_secret_from_dashboard'
// Canonical webhook envelope
const payload = JSON.stringify({
id: 'evt_test_001',
object: 'event',
type: 'payment.capture.completed',
api_version: '2026-10-29',
livemode: false,
created: Math.floor(Date.now() / 1000),
data: {
object: {
id: 'txn_test_001',
object: 'payment',
amount: '25.0000',
net: '24.1300',
currency: 'USD',
status: 'completed',
livemode: false,
},
},
})
// Watenga-Signature: t=<unix>,v1=HMAC_SHA256(secret, `${t}.${rawBody}`)
const t = Math.floor(Date.now() / 1000)
const v1 = crypto
.createHmac('sha256', secret)
.update(`${t}.${payload}`, 'utf8')
.digest('hex')
fetch('http://localhost:3000/webhooks/watenga', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Watenga-Signature': `t=${t},v1=${v1}`,
},
body: payload,
}).then(r => r.json()).then(console.log)
// Run: node test-webhook.jsTesting Webhook Retries
Watenga retries failed webhooks up to 5 times with exponential backoff. To test retry behaviour in sandbox:
- Set your webhook URL to a URL that returns a non-200 status (e.g.
httpstat.us/500) - Trigger a
payment.capture.completedevent by completing a sandbox payment - Check the Webhook Events log in Developer Dashboard
- See the delivery attempts with timestamps and response codes
- Update your webhook URL to a working endpoint
- Use the "Resend" button in the Webhook Events log to replay the event
Testing Refunds
To test a refund:
- Complete a sandbox payment using test card 4111 1111 1111 1111
- Note the transaction ID from the response or from GET /v1/transactions
- Call
POST /v1/transactions/{id}/refund - Verify the transaction status changes to
reversed - Check that the merchant balance is reduced by the refunded amount
curl -X POST https://api.watenga.africa/v1/transactions/txn_01HQXYZ/refund \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Customer request"}'Payouts
Payouts are dashboard-only
payment.payout-item.succeeded, failed or returned).Sandbox Reset
Data hygiene
Going Live Checklist
Before switching from sandbox to live:
- All test scenarios pass — approvals, declines, refunds, webhooks
- Webhook signature verification implemented and tested
- Idempotency keys used on all payment creation requests
- Error handling covers all error codes in the Errors reference
- Duplicate payment prevention tested (same merchantTransactionId twice)
- Mobile money polling implemented with timeout and cancel handling
- KYC approved in merchant dashboard (check Status in dashboard sidebar)
- Live API app created in Developer Console (not sandbox)
- Live keys saved securely (never in code — use environment variables)
- Webhook URL updated to production URL in live app settings
- Test with a real small transaction ($1.00) before going fully live
See also: Error codes, Webhooks, API Versioning.
Get notified when InnBucks and OneMoney go live.