Guide · Getting started

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

  1. Create a payment intent on your server.
  2. Send the customer to hosted checkout (or let an M-Pesa STK push prompt them).
  3. Confirm the outcome server-side — never from the browser alone.
  4. Fulfil the order when a signed payment.succeeded webhook 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_… or nup_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.
email
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
}
A successful creation only means the payment was initiated — it never means the customer has paid. Always confirm the outcome before fulfilling.

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 pending until 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 pending or processing.
  • On failed, let the customer retry — reuse the same Idempotency-Key rather 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 email for Paystack or phone for 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.

Next steps