- Create a payment with one request: an amount in NSH, your order reference, and the company name the buyer sees.
- Use a
nsh_test_key in Sandbox and ansh_live_key in Live. The requests and responses are the same. - A live payment returns a
checkout_url. Send the buyer there; the payment becomespaidwhen they pay. - Nuru sends
payment.paidandpayment.refundedto your https webhook. Confirm withGET /v1/payments/:idbefore you deliver. - 100 NSH = 1 USD. The company pays 1% of each paid payment. Everything else is free.
01Introduction
The Nuru Shillings API lets a website or app take payment in Nuru Shillings (NSH). Your server creates a payment, the buyer pays it from their Nuru wallet, and Nuru tells your server the result. The buyer sees your company name and the amount. Their own name is not shown to you or written to a public ledger.
A payment moves through these steps:
- Your server calls
POST /v1/paymentswith the amount, your reference, and your company name. - Nuru returns a payment with an id. In Live the status is
pendingand the response includes acheckout_url. - You send the buyer to the
checkout_url. They log in to their wallet and confirm the payment. - The status becomes
paid, the amount less the 1% fee is added to your balance, and Nuru sendspayment.paidto your webhook. - Your server confirms the payment and delivers the order.
Everything in this document can be tried in Sandbox first, with practice money and no verification.
02Before you start
Open an account
Go to Start free and enter your email. We send a sign-in link; opening it creates your account. There is no password and no cost. Every account starts with a wallet and 10,000 Sandbox NSH.
Open Business tools
From your wallet, choose Business tools. This is where you create keys, save a webhook, create payments by hand, refund them, and see every event Nuru sent. The Sandbox / Live switch at the top decides which mode you are working in.
Verify before Live
Sandbox works immediately. Before you can create a live key, you confirm the name on the account and your country, once. Verification is free. Until it is done, live money cannot move in or out of the account.
03Quick start
This takes about two minutes in Sandbox.
- In Business tools, switch to Sandbox and choose Create key. Copy the key; it is shown once.
- Store it on your server as an environment variable, for example
NURU_SECRET_KEY. - Create a payment:
curl https://norashillings.benuru.com/v1/payments \
-H "Authorization: Bearer $NURU_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "1800",
"unit": "NSH",
"reference": "order_1042",
"company": "Northwind Books"
}'
In Sandbox the payment is paid at once and 1,782 Sandbox NSH are added to your balance. The same call in Node.js:
const res = await fetch("https://norashillings.benuru.com/v1/payments", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NURU_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: "1800",
unit: "NSH",
reference: "order_1042",
company: "Northwind Books",
}),
});
const payment = await res.json();
04Rate and amounts
The rate is fixed: 100 Nuru Shillings equal 1 US dollar. It is the same when a buyer adds money, pays, receives a refund, or withdraws. There is no spread and no second rate.
- Amounts are whole numbers of NSH, sent as strings, for example
"1800". Strings avoid rounding errors in JSON. - The smallest amount is
"1"(0.01 USD). The largest single payment is"100000000"(1,000,000 USD). - Decimals, negative numbers, and leading signs are refused.
- To convert a price in dollars, multiply by 100: 18.00 USD is
"1800".
05Fees
Opening an account, the wallet, Business tools, the API, Sandbox, adding money, and withdrawing are free. There is no monthly fee.
The company pays 1% of each payment that reaches paid, rounded to the nearest whole NSH. The buyer pays the listed amount and no Nuru fee. A payment that is never paid costs nothing. A refund returns the fee to you.
| Amount | Fee | You receive |
|---|---|---|
| 49 NSH | 0 NSH | 49 NSH |
| 250 NSH | 3 NSH | 247 NSH |
| 1,800 NSH | 18 NSH | 1,782 NSH |
| 10,000 NSH | 100 NSH | 9,900 NSH |
Every payment object shows its own fee, so you never need to calculate it yourself.
06Sandbox and Live
Each account has two separate balances and two separate sets of keys. The mode is decided by the key you send, not by the URL.
| Sandbox | Live | |
|---|---|---|
| Key prefix | nsh_test_ | nsh_live_ |
| Money | Practice NSH; starts at 10,000 | Real NSH at 100 NSH = 1 USD |
| Verification | Not needed | Needed before a live key exists |
| New payment | paid at once | pending with a checkout_url |
| Webhook | Its own URL | Its own URL |
| Can affect the other mode | No | No |
Build and test against Sandbox. When you go live, change the key. The fields, routes, and responses stay the same.
07Authentication
Send your secret key in the Authorization header of every request:
Authorization: Bearer nsh_live_••••••••••••••••4f2a
- Shown once. Nuru stores only a one-way hash of the key and its last four characters. If you lose it, create a new one.
- One key per mode. Creating a key with Replace key stops the previous key in that mode immediately. Update your server before you replace a key that is in use.
- Server only. Never place a key in a web page, a mobile app, a public repository, or a log. Anyone with the key can create payments and refunds on your account.
- Live keys need verification. A live key on an account that is not verified is refused with status 403.
08Requests and responses
- Base URL:
https://norashillings.benuru.com/v1 - Use https. Send request bodies as JSON with
Content-Type: application/json. - Every response is JSON. A successful request returns status 200.
- A failed request returns a 4xx or 5xx status and a body with one field,
error, that explains what to fix. See Errors. - Times are in UTC, in ISO 8601 format, for example
2026-10-10T09:30:00.000Z.
| Method and path | What it does |
|---|---|
POST /v1/payments | Create a payment |
GET /v1/payments/:id | Retrieve one payment |
POST /v1/refunds | Refund a paid payment |
POST /v1/webhooks | Save or clear the webhook URL for this mode |
GET /v1/webhooks | Read the saved webhook URL |
POST /v1/webhooks/test | Send a test event to the saved URL |
09The payment object
Every payment route returns a payment object with these fields:
| Field | Description |
|---|---|
id | Unique payment id, starting with pay_. Store it with your order. |
status | pending, paid, or refunded. |
amount | The amount the buyer pays, in NSH, as a string. |
unit | Always NSH. |
fee | The 1% fee you pay when the payment is paid, in NSH, as a string. |
company | The name the buyer sees at checkout. |
chain | Always private. The payment is not written to a public ledger. |
reference | Your own order id, or null if you did not send one. |
mode | test for Sandbox or live. |
created | When the payment was created. |
checkout_url | Present only while the status is pending. The page where the buyer pays. |
Statuses
| Status | Meaning | Next |
|---|---|---|
pending | Created in Live and waiting for the buyer. No money has moved. | Becomes paid when the buyer pays. |
paid | The buyer’s balance was charged and yours was credited, less the fee. | Can be refunded once. |
refunded | The full amount went back to the buyer and the fee came back to you. | Final. |
Treat any status other than paid as not paid.
10Create a payment
POST /v1/payments
| Parameter | Required | Description |
|---|---|---|
amount | Yes | Whole NSH as a string, from "1" to "100000000". |
company | Yes | The name the buyer sees. Up to 80 characters. |
reference | No | Your order id, up to 100 characters. Do not put personal data in it. |
unit | No | If sent, must be NSH. |
Request:
{
"amount": "1800",
"unit": "NSH",
"reference": "order_1042",
"company": "Northwind Books"
}
Response with a live key:
{
"id": "pay_8f2c41d09a7be3150c66",
"status": "pending",
"amount": "1800",
"unit": "NSH",
"fee": "18",
"company": "Northwind Books",
"chain": "private",
"reference": "order_1042",
"mode": "live",
"created": "2026-10-10T09:30:00.000Z",
"checkout_url": "https://norashillings.benuru.com/pay?id=pay_8f2c41d09a7be3150c66"
}
With a test key the same request returns "status": "paid" and "mode": "test", with no checkout_url, and Nuru sends payment.paid to your Sandbox webhook straight away.
Each call creates a new payment. Before you create a second payment for the same order, check whether you already stored a payment id for it.
11Retrieve a payment
GET /v1/payments/pay_8f2c41d09a7be3150c66
Returns the payment object. You can only read payments that belong to your account and to the mode of the key you send. Use this call to confirm a payment before you deliver an order, and to check the status if a webhook did not arrive.
12Checkout
A live payment is paid on Nuru’s checkout page, at the checkout_url in the response. Redirect the buyer there, or show it as a link or button.
- The page shows your company name and the amount.
- A buyer without an account can create one on the spot with their email. They return to the same checkout after opening the sign-in link.
- The buyer must be verified and have enough live balance. If not, the page tells them what to do and nothing is charged.
- A payment can be paid only once. A second attempt is refused.
- You cannot pay your own payment.
Do not treat the buyer returning to your site as proof of payment. Wait for the webhook, or check with GET /v1/payments/:id.
13Refund a payment
POST /v1/refunds
{
"payment": "pay_8f2c41d09a7be3150c66"
}
Returns the payment object with "status": "refunded", and sends payment.refunded to your webhook.
- Only a
paidpayment can be refunded, and only once. - The refund is always the full amount. The buyer receives everything they paid, at the same rate.
- Your balance is reduced by the amount you received, and the fee is returned to you, so the refund costs you nothing extra.
- Your balance in that mode must cover the amount you received. If it does not, the refund is refused and nothing changes.
- You can also refund from the payments list in Business tools.
14Webhooks
A webhook is a URL on your server that Nuru calls when a payment changes. Sandbox and Live each have their own URL, set with the key of that mode or in Business tools.
Save, read, or clear the URL
POST /v1/webhooks
{
"url": "https://your-platform.example/nuru"
}
Returns {"url": "…", "mode": "live"}. Send an empty url to stop webhooks for that mode. GET /v1/webhooks returns the saved URL. The URL must:
- use https;
- use a public host name, not an IP address,
localhost, or a.localor.internalname; - be at most 500 characters.
Events
| Type | Sent when |
|---|---|
payment.paid | A payment becomes paid. |
payment.refunded | A payment is refunded. |
webhook.test | You call POST /v1/webhooks/test or choose Send test event. |
Nuru sends a POST request with a JSON body and these headers: Content-Type: application/json, User-Agent: NuruShillings-Webhooks/1, and Nuru-Event-Id.
POST https://your-platform.example/nuru
{
"id": "evt_3b91e07c55d2a8f41b20",
"type": "payment.paid",
"created": "2026-10-10T09:31:12.000Z",
"data": {
"id": "pay_8f2c41d09a7be3150c66",
"status": "paid",
"amount": "1800",
"unit": "NSH",
"company": "Northwind Books",
"chain": "private",
"reference": "order_1042"
}
}
A webhook.test event has "data": {"test": true} instead of a payment.
Delivery
- Nuru makes one attempt per event and waits up to 5 seconds for a reply. Redirects are not followed.
- Reply with any 2xx status as soon as you receive the event, then do slow work afterwards.
- Every event and the status your server returned are listed under Webhook events in Business tools.
- If your server was down, nothing is lost: read the payment with
GET /v1/payments/:id.
Handling an event safely
- Read
data.idfrom the event. - Call
GET /v1/payments/:idwith your secret key. Trust that response, not the event body. - Check that the
statusispaid, and that thereferenceandamountmatch the order you stored. - Record the event id. If the same id arrives again, ignore it.
- Deliver the order.
app.post("/nuru", express.json(), async (req, res) => {
res.sendStatus(200);
const event = req.body;
if (event.type !== "payment.paid" || await seen(event.id)) return;
const url = `https://norashillings.benuru.com/v1/payments/${event.data.id}`;
const r = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.NURU_SECRET_KEY}` },
});
const payment = await r.json();
const order = await findOrder(payment.reference);
if (payment.status === "paid" && order && order.amount === payment.amount) {
await deliver(order);
}
});
15Errors
A failed request returns a status code and a message written for the developer:
{
"error": "amount must be a whole number of NSH as a string, for example \"1800\"."
}
| Status | Meaning | Common causes |
|---|---|---|
| 400 | The request cannot be done as sent | Missing or invalid amount or company; unit other than NSH; refunding a payment that is not paid; balance too low for a refund; webhook URL not allowed |
| 401 | No valid key | Missing Authorization header; mistyped key; key was replaced |
| 403 | Not allowed yet | Live key used before the account is verified |
| 404 | Not found | Payment id does not exist, belongs to another account, or belongs to the other mode; unknown route |
| 500 | Problem on Nuru’s side | Try again. If it continues, write to support with the time and route |
Requests that fail with 4xx do not change anything. Fix the request before you retry it.
16Going live
- Your integration works end to end in Sandbox, including a refund and a webhook.
- Your account is verified, and you created a live key in Business tools with the switch on Live.
- The live key is stored on your server only, and not in your code repository.
- You saved a live webhook URL and it passes Send test event.
- You send buyers to the
checkout_urland deliver only after a confirmedpaid. - Duplicate events cannot deliver an order twice.
- Your site shows your company name, your terms, and your refund policy.
Read the Security page for how to keep keys and webhooks safe, and the Terms for the rules that apply to taking payments.
17Support and changes
Questions about the API or a live payment go to supportnorashillings@benuru.com. Include the payment id and the time of the request. Never send your secret key.
This document describes version 1. We may add new fields to responses and new event types without notice, so your code should ignore fields it does not recognise. If we need to remove or change an existing field, we will announce it by email and on this page before it takes effect.
Nuru Shillings · Documentation · Payments API version 1 · 10 October 2026