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.
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
The compatible address https://platform.nuepay.net/api/v1 remains available. Both accept the same requests. The API subdomain requires DNS and SSL setup.
Start with a simulated collection
- Open API Keys and choose Sandbox Key beside your application. Validate your PIN and copy the test key.
- Set
NUEPAY_TEST_KEYon your development server. Choose a new UUID for the payment. - 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 use separate records and balances. They never call Yo Payments or alter live wallets.
Sandbox playground
Test keys only. The key stays in this page's memory and is not saved in browser storage. Clear it when you finish.
Request ID:
Endpoints
Send JSON and Authorization: Bearer YOUR_SECRET. Amounts are decimal strings in UGX; payouts must be whole shillings.
POST /collectionsCollect mobile money into the wallet
POST /disbursementsSend mobile money from the wallet
POST /quotesPreview the fee and exact charge or debit without moving funds
GET /applicationIdentify the authenticated application and environment
GET /account/balanceRead the shared account balance in this environment
GET /transactions/{reference}Read a payment created by this application in this environment
POST /sandbox/transactions/{reference}/simulateSimulate 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.
For existing integrations, X-NuePay-Account with X-NuePay-Secret, and X-NuePay-Request-Id, continue to work. When using both idempotency headers, their UUIDs must match. The response's data.request_id is the original idempotency UUID; X-Request-Id identifies the current HTTP attempt.
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.
- Read the exact raw request body and
X-NuePay-Timestamp. Reject timestamps more than five minutes from your server clock. - Compute hexadecimal HMAC-SHA256 of
timestamp + "." + raw_bodyusing your webhook signing secret. - Compare
"v1=" + digestwithX-NuePay-Signatureusing constant-time comparison. - Match body
idwithX-NuePay-Event-Id. Store processed event IDs and accept only newer paymentversionvalues. - 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 a support 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.
| Code | Action |
|---|---|
| invalid_credentials | Check the environment and current secret. |
| validation_error | Correct the fields listed in errors. |
| idempotency_conflict | Reuse the original payload, or start a separate payment with a new UUID. |
| insufficient_funds | Ensure the balance covers the principal and fee. |
| payout_setup_required | Complete verified bank payout setup. |
| provider_unavailable | Wait for live processing to resume; do not create duplicate requests. |
| test_key_required | Use 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.

