How to integrate the NunuaPay Payment API
A complete, copy-paste path from creating a payment to confirming it paid — using one API that routes to Paystack, PayHero or M-Pesa depending on what you have configured.
What you will build
- Create a payment intent on your server.
- Send the customer to hosted checkout (or let an M-Pesa STK push prompt them).
- Confirm the outcome server-side — never from the browser alone.
- Fulfil the order when a signed
payment.succeededwebhook arrives.
Prerequisites
- A NunuaPay account with a business created.
- At least one provider configured for that business (Paystack, PayHero and/or M-Pesa).
- An API key for the environment you are integrating against:
nup_test_…ornup_live_….
1. Authentication
Integration endpoints use a scoped API key in the Authorization header. The key carries an environment, scopes and optional branch restrictions. The secret is shown once when the key is created.
Authorization: Bearer nup_live_<lookup>_<secret>2. Identifiers
Business ID identifies your tenant. Branch ID identifies a location inside that business and is optional. A branch-scoped key must always send its branch.
3. Create a payment intent
amount is an integer in minor units — whole shillings for KES. Payment creation requires an Idempotency-Key header.
curl -X POST https://pay.nunuanow.com/v1/payment-intents \\
-H "Authorization: Bearer nup_live_<lookup>_<secret>" \\
-H "Idempotency-Key: order-1001" \\
-H "Content-Type: application/json" \\
-d '{
"businessId": "<businessId>",
"method": "card",
"amount": 1500,
"currency": "KES",
"email": "customer@example.com",
"merchantRef": "ORDER-1001"
}'Request fields:
- businessId
- Required. Your business identifier.
- method
- Required. One of
card,bank_transfer,mobile_money,ussd(availability depends on the provider). - amount
- Required. Positive integer, minor units (whole shillings for KES).
- currency
- Required. Three-letter code, e.g.
KES. - Required for Paystack (card/bank/USSD flows).
- phone
- Required for M-Pesa mobile money (STK push).
- branchId
- Optional. Attributes the payment to a branch.
- provider
- Optional. Pins routing when several providers offer the same method.
- merchantRef
- Optional. Your own reference for reconciliation.
- returnUrl
- Optional. Where the customer returns after hosted checkout.
- metadata
- Optional. Arbitrary JSON stored with the payment.
The response is 201 with the payment intent. The important fields:
{
"id": "nup_pay_...",
"status": "requires_action",
"amount": 1500,
"currency": "KES",
"method": "card",
"provider": "paystack",
"reference": "nup_pay_...",
"checkoutUrl": "https://checkout.paystack.com/...",
"providerRef": "...",
"merchantRef": "ORDER-1001",
"branchId": null
}4. Send the customer to checkout
- Hosted checkout (Paystack): redirect the browser to
checkoutUrl. - STK push (PayHero / M-Pesa): there is no URL — the customer receives an M-Pesa prompt. The intent stays
pendinguntil they confirm.
5. Verify server-side
POST /v1/payment-intents/:id/verify re-checks the transaction with the provider. Use it when the customer returns from checkout and before you fulfil an order.
curl -X POST https://pay.nunuanow.com/v1/payment-intents/<id>/verify \\
-H "Authorization: Bearer nup_live_<lookup>_<secret>"Statuses you may see:
requires_action, pending, processing, succeeded, failed, cancelled, expired. Treat only succeeded as paid. A processing or pending result is not a failure — re-check or wait for the webhook.
6. Handle webhooks
Register an endpoint with POST /v1/webhook-endpoints and store the signing secret. NunuaPay sends payment.succeeded and payment.failed with an X-NunuaPay-Signature header. Verify it before acting, and process idempotently by X-NunuaPay-Event-Id. The full flow is in the webhooks guide.
Handling failed and pending payments
- Do not fulfil on
pendingorprocessing. - On
failed, let the customer retry — reuse the sameIdempotency-Keyrather than minting a new request. - Reusing a key with a different body returns
409 idempotency_conflict.
Common errors
- invalid_api_key
- The key is wrong, revoked or from another environment.
- validation_error
- A required field is missing — for example
emailfor Paystack orphonefor mobile money. - method_not_available
- No configured provider offers that method for this business.
- provider_not_configured
- No provider credentials are set for this business.
- idempotency_conflict
- The same key was reused with a different body.
Test versus live
Test keys operate against provider test environments; live keys move real money. A live payment intent cannot be created with a test key. Run the full flow with test credentials before going live.