Skip to playground
NuePayAPI v1

Developer guide

Your first NuePay payment.

Collect and send mobile money through one API. Start with a sandbox key, simulate an outcome, then use your live key when you are ready.

One account. Different applications.

Give each website or application a named key in API Keys. NuePay identifies the application from its secret. You do not send its nickname or account number with Bearer authentication.

Each application has a stable application_id, its own transaction history and optional webhook. Renaming it or regenerating its secret preserves that ID. Applications share the merchant's wallet; their balances are not separate. You can have up to 10 named keys.

Keep live secrets on your server. Use np_test_ keys for sandbox payments and np_live_ keys for real payments. PIN validation protects key management in the dashboard.

Base URL: https://api.nuepay.net/v1

Start with a simulated collection

  1. Open API Keys and choose Sandbox Key beside your application. Validate your PIN and copy the test key.
  2. Set NUEPAY_TEST_KEY on your development server. Choose a new UUID for the payment.
  3. Send this request. No approval prompt is sent to the phone.
curl 'https://api.nuepay.net/v1/collections' \
  -H "Authorization: Bearer $NUEPAY_TEST_KEY" \
  -H 'Idempotency-Key: REPLACE_WITH_A_NEW_UUID' \
  -H 'Content-Type: application/json' \
  -d '{"amount":"500.00","currency":"UGX","phone":"0772000000","merchant_reference":"order-123"}'

HTTP 202 means accepted, with status: process. Copy data.reference from the response, then simulate success:

curl 'https://api.nuepay.net/v1/sandbox/transactions/YOUR_REFERENCE/simulate' \
  -H "Authorization: Bearer $NUEPAY_TEST_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"outcome":"success"}'

Use failed for a decline or timeout for an uncertain outcome. Timeout remains process and can later succeed or fail. Repeating a final result does not repeat a credit or refund. Live keys cannot use simulation.

Sandbox accounts start with UGX 1,000,000 in simulated funds, shared across the account's applications. Test collections and payouts never move real money.

Sandbox playground

Create a payment. Choose an outcome. See what happens.

Test funds only
  1. 1Connect
  2. 2Create
  3. 3Simulate

Connect your application

Get a test secret from API Keys ↗︎. It stays in this tab’s memory. Live secrets are blocked.

Create a test payment

Amounts & fees ↗︎

Connect your application to set the payment details.

Payment details

No phone prompt is sent. Collections credit test funds; disbursements use your test balance.

Payment UUID

Choose the outcome

Not created

Create a test payment to choose its outcome.

Simulated outcome
Test your webhook ↗︎

Endpoints

Send JSON and Authorization: Bearer YOUR_SECRET. Amounts are decimal strings in UGX; payouts must be whole shillings.

POST /collections

Collect mobile money into the wallet

POST /disbursements

Send mobile money from the wallet

POST /quotes

Preview the fee and exact charge or debit without moving funds

GET /application

Identify the authenticated application and environment

GET /account/balance

Read the shared account balance in this environment

GET /transactions/{reference}

Read a payment created by this application in this environment

POST /sandbox/transactions/{reference}/simulate

Simulate an outcome using a test key

Amounts, fees and safe retries

For collections, amount is the wallet credit. Fees are added to the payer's charge and the charge is rounded up to a whole shilling. For disbursements, amount is what the recipient receives; the wallet pays that amount plus the fee. The current maximum principal is UGX 5,000,000 per payment.

Call POST /quotes with {"type":"collection","amount":"500.00","currency":"UGX"} before displaying a charge. At the current collection fee, the wallet receives UGX 500 and the payer is charged UGX 520. The response explicitly includes payer_charge, wallet_credit, recipient_amount and wallet_debit as applicable. A quote does not reserve funds or lock a fee.

Keep the same Idempotency-Key UUID when retrying one payment after a connection error. A matching retry returns the original payment with HTTP 200. Reusing it for another payload or operation returns idempotency_conflict. Each application and environment has its own request scope. Regenerating a secret preserves existing payment identity.

Webhooks and payment outcomes

Save an HTTPS Web Hook URL in the application's edit dialog. Read its signing secret from the same dialog. Sandbox callbacks are off by default; explicitly enable “Also send sandbox events to this URL” to test your receiver. Every new event contains livemode; keep sandbox handling separate from live order fulfilment.

Events include collection.process, collection.success, collection.failed and their disbursement equivalents. Fulfil only after status: success. An Under review result stays process with status_reason: review_required. A new accepted request is not confirmation of payment.

  1. Read the exact raw request body and X-NuePay-Timestamp. Reject timestamps more than five minutes from your server clock.
  2. Compute hexadecimal HMAC-SHA256 of timestamp + "." + raw_body using your webhook signing secret.
  3. Compare "v1=" + digest with X-NuePay-Signature using constant-time comparison.
  4. Match body id with X-NuePay-Event-Id. Store processed event IDs and accept only newer payment version values.
  5. Return a 2xx response after saving the event durably. Delivery may be duplicated or arrive out of order.

Failed deliveries retry after 1 minute, 5 minutes, 15 minutes, 1 hour, 4 hours, 12 hours and then 24 hours, up to ten attempts. Inspect delivery history and PIN-protected retries on API Keys. API secret rotation and webhook secret rotation are separate actions.

Troubleshooting

Errors include a readable message, a stable code, and an HTTP attempt request_id. Validation errors also include errors by field. Match the ID with API request history in the dashboard. HTTP 429 means rate limited: wait before retrying, and retain the same payment idempotency UUID.

CodeAction
invalid_credentialsCheck the environment and current secret.
validation_errorCorrect the fields listed in errors.
idempotency_conflictReuse the original payload, or start a separate payment with a new UUID.
insufficient_fundsEnsure the balance covers the principal and fee.
payout_setup_requiredComplete verified bank payout setup.
provider_unavailableWait for live processing to resume; do not create duplicate requests.
test_key_requiredUse an np_test_ key for simulation.

Limits: 120 authenticated requests per key and environment per minute, 300 requests per IP per minute, and 20 failed authentication attempts per IP per minute. Disbursements require verified payout setup and sufficient funds. Live collection and disbursement availability follows account and provider status.