Pay with Nyuza.One checkout integration.
Authenticate, create an order, invoice and payment in one request, and send your customer to checkout.
Overview
Add Pay with Nyuza to your application with express checkout: authenticate, create a checkout, redirect the customer, and confirm payment. Nyuza creates the order, invoice and payment in a single request.
Before you begin
Start in Nyuza Console under Organization profile → API Clients. Create a client to generate your client ID and secret, then use those credentials to obtain a token. Have your organization identifier, institution, channel and payment-provider configuration ready, along with an HTTPS callback endpoint and a customer return page.
Where your code runs
| Component | Responsibility |
|---|---|
| Customer browser | Starts the purchase, opens paymentUrl and displays your return page. |
| Your backend | Keeps credentials, obtains the token, calls checkout, saves invoiceId/paymentId and verifies payment. |
| Nyuza checkout | Hosts the customer payment experience. |
| Your webhook endpoint | Receives lifecycle updates from Nyuza and updates your purchase records. |
Browser → your backend → Nyuza API. Your backend returns paymentUrl to the browser. Nyuza sends callbacks directly to your backend, even if the customer closes the browser.
Prepare your integration values
| Value | Where it comes from | How you use it |
|---|---|---|
| clientId / clientSecret | Console → Organization profile → API Clients | Authenticate from your backend. |
| organizationId | Your Nyuza organization configuration | Use the organization UUID in the checkout path; do not substitute the API client ID or organization display code. |
| institution / channel | Your integration configuration supplied by Nyuza | Send as headers. RGI is an example institution; use the channel assigned to your integration consistently. |
| paymentProviderId | Your configured payment provider in Nyuza | Selects the provider for invoice/payment creation. |
| paymentSource | Your supported integration source; the examples use WEB | Use the same source when configuring return URLs. |
| referenceNumber | Generated by your application | Use a purchase reference such as ORDER-DEMO-001. |
| request-id | Generated by your backend for the request | Keep it in diagnostic logs; it is not an authentication token or a documented idempotency key. |
| Callback URL / return URL | Routes implemented by your application | Backend notification endpoint / customer-facing result page. |
Choose your integration
| Approach | When to use it |
|---|---|
| Express checkout · recommended | Create the order, invoice and payment together, then redirect the customer to hosted checkout. |
| Separate steps | Manage order, invoice, payment and transaction creation independently when your use case requires it. |
Environment and examples
Express checkout nonproduction origin:
https://dev.nonprod.rolengiapps.comEndpoint paths are shown independently of the origin. Use the authentication and payment API environment URLs supplied during onboarding.
Use the environment URLs supplied during onboarding. Values in {{doubleBraces}} are placeholders; replace them with your configuration or transaction data. Example identifiers, amounts and enum values are illustrative.
NEXT STEPCreate your API credentials →Your first test payment
Follow one purchase, ORDER-DEMO-001, from setup to confirmation. Complete this in your test environment before accepting customer payments.
Walkthrough defaults
The examples below use provisional defaults to make the walkthrough concrete. Verify the URLs, units, required fields and response mappings against your environment before using them in production.
| Setting | Example default |
|---|---|
| API origin | https://dev.nonprod.rolengiapps.com |
| Authentication URL | https://dev.nonprod.rolengiapps.com/uhasibu/auth/token/opaque |
| Payment lookup URL | https://dev.nonprod.rolengiapps.com/uhasibu/v1/api/malipo/{paymentId} |
| Institution / channel / source | RGI / api / WEB |
| Token type | Opaque |
| Timestamp | Unix time in milliseconds, generated for each request. |
| Purchase amount | 1500 KES, treating amounts as major currency units for this example. |
| Purchase reference | ORDER-DEMO-001 |
| Step | Action | Expected result |
|---|---|---|
| 1. Prepare credentials | Create an API client in Console and save its client ID and secret on your backend. | Credentials ready for authentication. |
| 2. Prepare callbacks | Configure your public callback URL, headers and customer return URL in Console. | Your backend can receive notifications. |
| 3. Authenticate | Send auth.json using the authentication cURL example. | An authentication token to use in API requests. |
| 4. Create checkout | Submit a validated checkout.json with details.referenceNumber set to ORDER-DEMO-001. | The checkout result contains invoiceId, paymentId and paymentUrl. |
| 5. Store and redirect | Save both identifiers against ORDER-DEMO-001 and redirect the browser to paymentUrl. | The customer reaches Nyuza checkout. |
| 6. Complete the test | Follow the test payment procedure for your configured provider. | A payment outcome is available through callbacks or payment lookup. |
| 7. Confirm and replay | Verify the outcome, amount and currency, then resend the event from History. | Your purchase updates correctly and fulfillment happens only once. |
Keep a purchase record
{
"purchaseReference": "ORDER-DEMO-001",
"invoiceId": "{{invoiceId}}",
"paymentId": "{{paymentId}}",
"paymentUrl": "{{paymentUrl}}"
}This is an example record for your application, not an API response. Store the purchase’s expected amount and currency alongside it so you can verify the payment outcome.
Authentication
Create an API client first, then exchange its client ID and secret for a token.
Open API Clients ↗Create your API credentials
Sign in to Nyuza Console and open Organization profile → API Clients. Your organization can create as many API clients as needed, each with its own client ID and secret.
| Step | Action |
|---|---|
| 1. Open API Clients | Select your organization profile in the Console sidebar, then open API Clients. |
| 2. Create a client | Select Add Client to generate a client ID and secret. |
| 3. Save the credentials | Copy the client ID and client secret into your backend configuration. Keep the secret secure and out of browser code. |
| 4. Obtain a token | Send the credentials to the authentication endpoint below. Use the resulting token for API requests. |
Use separate API clients when you need independent credentials for different applications or integrations. The API Clients list provides Copy for the client ID, Copy Secret, and Delete actions.
Obtain an authentication token
Nyuza uses OAuth 2.0 with Keycloak. Client applications obtain an opaque token through the Nyuza authentication service.
/auth/token/opaqueRequest
Content-Type: application/json{
"clientId": "{{clientId}}",
"clientSecret": "{{clientSecret}}"
}| Field | Type | Description |
|---|---|---|
| clientId | string | Client identifier issued for the integration. |
| clientSecret | string | Client credential issued for the integration. |
Token request with cURL
Run this on your development machine or backend. Save your credentials as auth.json using the JSON structure above; keep that file out of source control. The URL below expands the authentication URL in the supplied test collection.
curl --request POST \
'https://dev.nonprod.rolengiapps.com/uhasibu/auth/token/opaque' \
--header 'Content-Type: application/json' \
--data-binary @auth.jsonStore the token returned by the authentication service on your backend and send it with its token type. Follow the expiry returned by the service; obtain a new token when needed. A new token does not require a new API client.
Authenticated requests
Send the token and its returned token type in the Authorization header. The integration reference uses Opaque as the default token type.
Authorization: {{tokenType}} {{authToken}}Payment API headers
| Header | Value or example |
|---|---|
| Authorization | {{tokenType}} {{authToken}} |
| institution | {{institution}} — example RGI |
| channel | {{channel}} — use your configured integration channel. |
| request-id | A request identifier, for example a UUID. |
| timestamp | {{timestamp}} — generate a current request timestamp. |
| Content-Type | application/json for JSON request bodies. |
Credential handling
Make authenticated requests from your application backend. Store client secrets and tokens securely and exclude them from logs. Keep request identifiers separate from authentication tokens.
Illustrative token response
Example response mapping only. The actual response may use a different envelope or field names; map the values returned by your authentication endpoint.
{
"access_token": "example-token",
"token_type": "Opaque",
"expires_in": 3600
}In this example, access_token becomes NYUZA_TOKEN and token_type becomes NYUZA_TOKEN_TYPE. expires_in is an illustrative lifetime in seconds, not a guaranteed Nyuza token lifetime.
NEXT STEPStart express checkout →Express checkout
invoiceId · paymentId · paymentUrl. Save the identifiers against your purchase, then redirect the customer to paymentUrl.
Jump to the checkout result →/duka-manager/v1/api/orders/{organizationId}/checkout/expressSubmit your order details using your organizationId in the path. Nyuza creates a new order, creates its invoice and payment, and saves the invoice and payment references against the order.
Send the request
| Header | Example |
|---|---|
| Authorization | {{tokenType}} {{authToken}} |
| accept | */* |
| institution | RGI |
| channel | {{channel}} — use your configured integration channel. |
| request-id | {{requestId}} |
| accept-language | en |
| timestamp | {{timestamp}} — generate a current request timestamp. |
| Content-Type | application/json |
The request combines order details with payment configuration. The full nested structure is available in Request reference.
| Field | Type | Description |
|---|---|---|
| details | object | Order lines, reference number, amounts and fulfillment details. details.referenceNumber is forwarded to invoice creation. |
| audit | object | Customer, member and organization context. |
| recommendedAmount | number | Copied to invoice metadata when supplied. |
| paymentProviderId | string | Payment provider identifier forwarded to invoice creation. |
| paymentSource | string | Payment source forwarded to invoice creation. |
| returnUrl | string | Customer return URL, copied to invoice metadata when nonblank. |
| standingOrderWalletRefId | string | Standing order wallet reference forwarded to invoice creation. |
| invoiceMetadata | object | Additional invoice metadata. See Request reference for precedence rules. |
| paymentAllocations | array | Provider, wallet and amount allocations forwarded to invoice creation. |
| organizationId | string | Organization identifier. |
Send checkout with cURL
Save your checkout JSON as checkout.json. Set NYUZA_ORGANIZATION_ID, NYUZA_TOKEN_TYPE, NYUZA_TOKEN, NYUZA_INSTITUTION, NYUZA_CHANNEL, NYUZA_REQUEST_ID and NYUZA_TIMESTAMP in your local shell. Use a fresh timestamp in the format required by your endpoint configuration.
curl --request POST \
"https://dev.nonprod.rolengiapps.com/duka-manager/v1/api/orders/${NYUZA_ORGANIZATION_ID}/checkout/express" \
--header "Authorization: ${NYUZA_TOKEN_TYPE} ${NYUZA_TOKEN}" \
--header "institution: ${NYUZA_INSTITUTION}" \
--header "channel: ${NYUZA_CHANNEL}" \
--header "request-id: ${NYUZA_REQUEST_ID}" \
--header "timestamp: ${NYUZA_TIMESTAMP}" \
--header 'accept-language: en' \
--header 'Content-Type: application/json' \
--data-binary @checkout.jsonSave the result and redirect
The checkout data object contains three fields:
{
"invoiceId": "{{invoiceId}}",
"paymentUrl": "{{paymentUrl}}",
"paymentId": "{{paymentId}}"
}| Field | Purpose |
|---|---|
| invoiceId | Identifier of the invoice created for the order. Retain it for invoice correlation. |
| paymentId | Identifier of the payment. Use it for payment lookup and reconciliation. |
| paymentUrl | Hosted checkout URL. Redirect the customer to this exact returned value. |
Save invoiceId and paymentId against your purchase, then redirect the customer to the exact paymentUrl returned. This URL identifies the hosted checkout for the invoice. A successful response means checkout has been initiated; confirm the payment before fulfillment.
Starter checkout body
Save this illustrative starter body as checkout.json and replace the identifier placeholders. It is a compact starting point, not a validated minimum schema; your organization may require additional order or audit fields.
{
"details": {
"referenceNumber": "ORDER-DEMO-001",
"description": "Demo service purchase",
"saleOrderLines": [
{
"productSaleOrders": [
{
"productName": "Demo service",
"productType": "SERVICE_PRODUCT",
"quantity": 1,
"price": 1500,
"total": 1500,
"fulfillmentType": "SALE"
}
],
"total": 1500
}
],
"total": 1500
},
"organizationId": "{{organizationId}}",
"paymentProviderId": "{{paymentProviderId}}",
"paymentSource": "WEB",
"returnUrl": "https://merchant.example/payment/result"
}Confirm payment
A browser return is not proof of payment. Verify the outcome, amount and currency using a callback or payment lookup.
Process payment updates →Use webhook notifications to follow payment lifecycle updates. Retrieve the payment when you need to check or reconcile its current outcome.
/uhasibu/v1/api/malipo/{paymentId}Use the paymentId returned by express checkout. Match the result to your purchase and verify the payment outcome, amount and currency before fulfillment. A customer reaching your return page does not confirm payment; they may also close checkout before returning.
Recover an interrupted checkout
Keep your purchase reference and request identifier. If checkout times out or fails after processing begins, reconcile any existing order, invoice or payment before creating another checkout. Records may already have been created.
Find payment and transaction records
/uhasibu/v1/api/malipo/checkout?page=0&length=20/uhasibu/v1/api/malipo/transactions?page=0&length=20These endpoints list payments and transactions respectively. Match records using the relevant payment or correlation identifier. A list response alone is not confirmation that the current payment succeeded.
Troubleshoot the first payment
| Symptom | What to check |
|---|---|
| Authentication rejected | Check the environment, client ID, secret and Authorization token type. Obtain a new token if the current one has expired. |
| Checkout request rejected | Read the returned error details; check organization, configured identifiers, required fields and JSON types before correcting the request. |
| Checkout times out | Record the purchase reference and request ID. Inspect existing payment records and Console history before starting another checkout; do not assume a timeout means nothing was created. |
| No callback reaches your app | Check that webhooks are enabled, the public HTTPS URL is reachable and the configured headers are accepted by your route. Inspect History for the delivery result. |
| Callback delivered but purchase unchanged | Inspect your handler logs and reference matching. Resolve the processing issue, then resend the event. |
| Duplicate fulfillment | Persist processing state against event references and make business actions safe to repeat. Preserve later lifecycle updates for the same payment. |
Illustrative payment mapping
The following is an application-side example, not a confirmed Nyuza response schema. Map the actual payment response into your own record before applying business rules.
{
"purchaseReference": "ORDER-DEMO-001",
"paymentId": "{{paymentId}}",
"status": "succeeded",
"amount": 1500,
"currency": "KES"
}For your application, a simple status model can use pending, succeeded and failed. These are local example labels, not documented API enum values. Map the provider’s actual statuses explicitly; keep unknown outcomes unresolved until verified.
NEXT STEPConfigure webhooks →Webhooks
Console → Settings → Tools & Integrations → Webhooks. Duplicate deliveries retain the same reference keys.
Open Webhooks ↗Configure and test your callback before starting the first checkout. This ensures your backend can receive the resulting lifecycle events.
Subscribe to lifecycle events so your backend receives payment updates independently of the customer’s browser. Configure a callback endpoint, process notifications, and use delivery history to investigate or resend events.
Configure in Console
Open console.nyuza.com and navigate to Settings → Tools & Integrations → Webhooks. On the Webhooks & Integrations page, open the Configuration tab.
| Step | Action |
|---|---|
| 1. Enable the webhook | Select Enable payment status webhook. |
| 2. Set the callback URL | Enter the HTTPS endpoint in your application that will receive webhook POST requests. |
| 3. Add callback headers | Use Add Header to supply any authentication or routing headers required by your endpoint, such as X-API-KEY. These headers are included with each webhook request. |
| 4. Set payment return URLs | Use Add URL to configure a customer return URL for each applicable source and channel. Match these values to your integration configuration. |
| 5. Save | Select Save Settings to apply the configuration. |
Understand the URLs
| URL | Purpose |
|---|---|
| paymentUrl | Returned by express checkout. Redirect the customer here to pay. |
| returnUrl | Your customer-facing page after checkout. Set it in the checkout request or configure return URLs by source and channel in Console. |
| Callback URL | Your backend endpoint for webhook POST requests. Configure it in Console or through merchant settings. |
Process lifecycle events
Validate your configured authentication headers and match the notification to the stored payment and purchase. Use the reported payment outcome to update your records, and verify amount and currency before fulfillment.
Duplicate deliveries, including manual resends, retain the same reference keys. Record successful processing against those references and acknowledge duplicates without repeating fulfillment or other business actions. If processing previously failed, allow the resent event to complete the work.
A payment can receive multiple lifecycle updates. Deduplicate the same event while allowing later updates for that payment; paymentId alone is not sufficient to distinguish them.
Review history and resend
Open the History tab under Settings → Tools & Integrations → Webhooks. Each row shows the callback time, payment ID, destination URL, HTTP status and delivery success. Filter by a preset period or date range, then select Search. Use the page controls to browse results.
To redeliver an event, locate its row, check the payment ID and destination, and select Resend. The resent event keeps the same reference keys and does not create a new payment.
HTTP status and Success describe callback delivery. A 200 response confirms that your endpoint responded successfully; determine payment success from the event data or payment lookup.
Configure through the API
/uhasibu/v1/api/malipo/settings/merchantConfigure your application callback destination and customer return URL using the shared payment API headers.
{
"webHookSettings": {
"enabled": true,
"paymentStatusCallbackUrl": "{{callbackUrl}}",
"notifyAnkaraStatus": false,
"headers": {
"X-Client-Key": "{{callbackKey}}"
}
},
"paymentReturnUrlSettings": [
{
"paymentSource": "WEB",
"channel": "{{channel}}",
"returnUrl": "{{returnUrl}}"
}
]
}The example enables external callbacks and disables Ankara notification routing. The headers object sets custom headers for callback requests; X-Client-Key is an example.
/uhasibu/v1/api/malipo/settings/merchantRead back the settings after configuration and verify the callback URL, headers and return URL.
Test callbacks during local development
Implement a POST route on your backend, for example /webhooks/nyuza. Make it reachable through a public HTTPS development endpoint or tunnel; Nyuza cannot reach a localhost address on your laptop. Configure that public URL and your chosen authentication header in Console.
Send a local test request to verify your route accepts JSON and validates the header. Then complete a test checkout using the test procedure supplied for your configured provider. Inspect Console → Webhooks → History and your application logs. Select Resend to verify that the same event does not repeat fulfillment.
A delivery test checks connectivity. Verify business processing separately: the callback must match the purchase, update the payment outcome correctly and handle repeated deliveries.
Illustrative event mapping
Use this example to structure your handler’s internal event record. It does not define the Nyuza webhook payload or its reference-key names.
{
"eventReference": "example-event-001",
"purchaseReference": "ORDER-DEMO-001",
"paymentId": "{{paymentId}}",
"eventType": "payment.updated",
"status": "succeeded",
"amount": 1500,
"currency": "KES"
}Map the webhook’s actual duplicate reference keys to your internal eventReference. A resend maps to the same eventReference; a distinct lifecycle event must remain distinguishable. The example eventType and status are illustrative labels.
Separate-step integration
Choose this approach when you need control over each creation stage: create an order, create its invoice, create a payment, then initiate a payment transaction. The payment operations below follow order and invoice creation. Use the authenticated headers from Authentication.
Create a payment
/uhasibu/v1/api/malipo{
"externalReference": "{{externalReference}}",
"amount": {"amount": 1500, "currency": "KES"},
"paymentSource": "WEB",
"description": "{{paymentDescription}}",
"requestPayment": false,
"phoneNumber": "{{paymentPhoneNumber}}",
"paymentTerm": "PREPAID",
"allowSplitPayments": true
}Retain the payment identifier for the transaction request and subsequent lookup.
Create a transaction
/uhasibu/v1/api/malipo/transactions{
"paymentRefId": "{{paymentId}}",
"paymentProviderType": "MPESA",
"paymentSource": "WEB",
"description": "{{paymentDescription}}",
"amount": {"amount": 1500, "currency": "KES"},
"externalTransactionId": "{{externalTransactionId}}",
"correlationId": "{{correlationId}}",
"customerRefId": "{{customerRefId}}",
"requestPayment": true,
"phoneNumber": "{{paymentPhoneNumber}}"
}paymentRefId links the transaction to the payment record. Store the payment, transaction, external reference and correlation identifiers separately. The example uses MPESA, WEB, PREPAID and KES.
Request reference
Use this reference for the full express checkout structure and optional invoice metadata behavior. Include the fields needed for your use case and replace the example values with transaction data.
The full body is a field reference, not a minimum-required request. Required fields depend on request validation and your configuration. Use a validated request for your organization when testing.
Understand the amount fields
| Field | Role |
|---|---|
| quantity / price | Describe the product quantity and price. |
| total / discount | Appear at product, order-line and order levels. Keep values consistent with your purchase and the API’s calculation rules. |
| recommendedAmount | Passed into invoice metadata; it does not itself confirm a charge. |
| minimumOrderAmount | Contributes to the minimumAmount metadata calculation described below. |
| paymentAllocations[].amount | Contains an allocation amount and currency. These allocations are forwarded to invoice creation. |
Use the currency and amount units configured for your payment integration. Do not infer amount units or charge calculation rules from the illustrative values in the field reference.
Invoice metadata
Nyuza copies invoiceMetadata, then applies the dedicated request values and the order-derived minimum. Other metadata keys are preserved.
| Metadata key | Value used | Precedence |
|---|---|---|
| recommendedAmount | The request recommendedAmount, when non-null. | Replaces invoiceMetadata.recommendedAmount when supplied. Otherwise the existing metadata value is retained. |
| returnUrl | The request returnUrl, when non-null and nonblank. | Replaces invoiceMetadata.returnUrl when supplied. Otherwise the existing metadata value is retained. |
| minimumAmount | The sum of positive minimumOrderAmount values across the saved order’s product entries. | Replaces invoiceMetadata.minimumAmount when the sum is positive. Otherwise the existing metadata value is retained. |
Minimum amount
Each positive minimumOrderAmount contributes once to the sum. Missing, zero and negative values are excluded. Quantity is not multiplied into this calculation.
| Product entry | Quantity | minimumOrderAmount | Contribution |
|---|---|---|---|
| A | 3 | 100 | 100 |
| B | 1 | 50 | 50 |
| C | 2 | 0 | 0 |
| Total | — | — | 150 |
This example produces minimumAmount = 150. The calculation uses the saved order’s product entries.
Full request structure
{
"details": {
"saleOrderLines": [
{
"productSaleOrders": [
{
"productVariantId": "string",
"productName": "Demo service",
"productType": "SERVICE_PRODUCT",
"variantName": "string",
"quantity": 1,
"price": 1500,
"discount": 0,
"total": 1500,
"minimumOrderAmount": 0,
"fulfillmentType": "SALE",
"inventoryUnitId": "string",
"serviceAssetAssignmentId": "string",
"billable": true,
"costed": true
}
],
"discount": 0,
"total": 1500
}
],
"serviceAssetAssignmentId": "string",
"referenceNumber": "ORDER-DEMO-001",
"discount": 0,
"total": 1500,
"favorite": true,
"description": "Demo purchase",
"fulfillmentMethod": "PICKUP",
"deliveryAddress": "string",
"preferredRiderPhone": "string",
"deliveryEstimate": 0,
"fulfillmentAddress": "string",
"fulfillmentContactName": "string",
"fulfillmentContactPhone": "string",
"fulfillmentInstructions": "string",
"requestedFulfillmentAt": "string",
"fulfillmentFee": 0
},
"audit": {
"customers": [
"string"
],
"agentMemberId": "string",
"creatorMemberId": "string",
"organizationId": "string",
"stageId": "string",
"organizationBranchId": "string"
},
"recommendedAmount": 1500,
"paymentProviderId": "paymentProviderId",
"paymentSource": "WEB",
"returnUrl": "https://merchant.example/payment/result",
"standingOrderWalletRefId": "string",
"invoiceMetadata": {
"additionalProp1": {},
"additionalProp2": {},
"additionalProp3": {}
},
"paymentAllocations": [
{
"standingOrderWalletRefId": "string",
"paymentProviderId": "string",
"amount": {
"amount": 1500,
"currency": "KES"
}
}
],
"organizationId": "string"
}