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

With 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 numberBrandWhat happens
4111 1111 1111 1111VisaApproved: payment.capture.completed
4012 0000 3333 0026VisaApproved: payment.capture.completed
5200 0000 0000 0007MastercardApproved: payment.capture.completed
4000 0000 0000 0002VisaDeclined by the issuer (do not retry): payment.capture.declined
5200 0000 0000 0114MastercardDeclined by the issuer (do not retry): payment.capture.declined
4000 0000 0000 0069VisaDeclined by the issuer (do not retry): payment.capture.declined
4000 0000 0000 0127VisaDeclined by the issuer (do not retry): payment.capture.declined
4000 0000 0000 9995VisaDeclined, insufficient funds (the buyer may retry)
4000 0000 0000 0119VisaProvider 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

NumberInternationalWhat happens
0771111111+263771111111Approved at once: payment.capture.completed
0772222222+263772222222Pending for about 30 seconds (onPending), then approved
0773333333+263773333333The buyer refuses the prompt (onCancel): payment.capture.cancelled
0774444444+263774444444Insufficient 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)

bash
# 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/watenga

Option 2 — Cloudflare Tunnel (free, no account needed)

bash
npx cloudflared tunnel --url http://localhost:3000

# Gives you a permanent-ish URL for the session

Node.js webhook test script:

javascript
// 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.js

Testing Webhook Retries

Watenga retries failed webhooks up to 5 times with exponential backoff. To test retry behaviour in sandbox:

  1. Set your webhook URL to a URL that returns a non-200 status (e.g. httpstat.us/500)
  2. Trigger a payment.capture.completed event by completing a sandbox payment
  3. Check the Webhook Events log in Developer Dashboard
  4. See the delivery attempts with timestamps and response codes
  5. Update your webhook URL to a working endpoint
  6. Use the "Resend" button in the Webhook Events log to replay the event

Testing Refunds

To test a refund:

  1. Complete a sandbox payment using test card 4111 1111 1111 1111
  2. Note the transaction ID from the response or from GET /v1/transactions
  3. Call POST /v1/transactions/{id}/refund
  4. Verify the transaction status changes to reversed
  5. Check that the merchant balance is reduced by the refunded amount
bash
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

Payouts to your bank or wallet are made in the dashboard, with two-step verification. They are not available through the API, the SDKs or the CLI. Each payout's result is reported to your webhook (payment.payout-item.succeeded, failed or returned).

Sandbox Reset

Data hygiene

There is no guaranteed automatic daily wipe of sandbox transactions. Treat sandbox data as durable until ops resets it. Contact support if you need a clean merchant sandbox.

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.