REST API
Call the Nylon Pay REST API directly from your server
The REST API runs the same operations as the SDK: collect a payment, send a payout, check a transaction, list transactions, and verify a phone number. Every call goes from your server to ours with an access token.
Read Limitations before you choose REST.
1. How a call is shaped
Every operation is one POST to https://api.nylonpay.com/api/services. The body names the operation with service and action, and carries the input in payload. For the REST API, service is always "rest".
curl -s https://api.nylonpay.com/api/services \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"intent": "execute",
"service": "rest",
"action": "get-status",
"payload": {
"reference": "11db1c21-7f43-490c-b76e-485baa326edf"
}
}'A successful call answers 200 with the result in data. A refused call answers 400 and status is false.
| Operation | action |
|---|---|
| Exchange keys for an access token | auth |
| Collect a payment | collect-payment |
| Send a payout | make-payout |
| Get payment status | get-status |
| Get a transaction | get-transaction |
| List transactions | list-transactions |
| Verify a phone number | verify-phone |
2. Get an access token
Create a key in Dashboard > API Keys, choose REST API under Use with, then Sandbox or Live. Add allowed IP addresses to a key to accept its calls from those addresses only. A sandbox key made for the SDK also works on the REST API.
Exchange the key and secret for a token with auth:
curl -s https://api.nylonpay.com/api/services \
-H "Content-Type: application/json" \
-d '{
"intent": "execute",
"service": "rest",
"action": "auth",
"payload": {
"apiKey": "npk_live_...",
"apiSecret": "nps_live_..."
}
}'Send the token on every other call as Authorization: Bearer <accessToken>.
- Calls are server to server. We refuse them from a browser.
- A token is valid for up to one hour.
expiresInis in seconds. - Use one key per server calling the API.
- When a call is refused for authentication, get a new token with
authand retry the same request once, with the samereference. If it is refused again, surface the error.
3. Operations
Collect a payment
collect-payment starts a payment from the customer's mobile money or bank account.
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | yes | Amount in the smallest currency unit. Minimum 500 UGX |
currency | string | yes | ISO 4217 currency code (e.g. "UGX") |
description | string | yes | Shown to the customer |
customer.name | string | yes | Customer full name |
customer.phoneNumber | string | yes | Any common format accepted, auto-normalized to the market's calling code (256… for UGX). See Phone Number Format |
customer.email | string | no | Customer email address |
method | string | no | "mobileMoney" or "bank" |
reference | string | yes | UUID of any version. Same reference, same payment |
metadata | object | no | Custom string key-value pairs attached to the transaction |
tags | string[] | no | Up to 10 labels for filtering. See Smart Tags |
testOutcome | "success", "fail", or a Nylon failure code | no | Force the sandbox result while testing. Omit it and the sandbox picks success about 70% of the time. Sandbox keys only |
prompt | "pin" | no | Keep the sandbox payment pending until you enter the customer PIN. See Sandbox. Sandbox keys only |
curl -s https://api.nylonpay.com/api/services \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"intent": "execute",
"service": "rest",
"action": "collect-payment",
"payload": {
"amount": 50000,
"currency": "UGX",
"description": "Order #12345",
"customer": {
"name": "John Doe",
"phoneNumber": "+256700000000",
"email": "customer@example.com"
},
"reference": "11db1c21-7f43-490c-b76e-485baa326edf",
"metadata": { "orderId": "12345" }
}
}'A second call with the same reference answers the same payment, marked duplicate: true. It carries the response recorded when the payment was created, so check Get payment status for the current state.
Send a payout
make-payout sends money to a customer's mobile money account or bank account.
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | yes | Amount in the smallest currency unit. Minimum 5,000 UGX for mobile money, 50,000 UGX for bank transfers |
currency | string | yes | ISO 4217 currency code (e.g. "UGX") |
description | string | yes | Payout description |
customer.name | string | yes | Recipient full name |
customer.phoneNumber | string | yes | Recipient phone number. Any common format accepted, auto-normalized |
customer.email | string | no | Recipient email address |
destination.accountHolderName | string | yes | Name on the receiving account |
destination.accountNumber | string | yes | Mobile money number or bank account number |
destination.bankName | string | no | Bank name, required for bank account payouts |
destination.phone | string | yes | Recipient phone number, auto-normalized |
reference | string | yes | UUID of any version. Same reference, same payout |
metadata | object | no | Custom string key-value pairs attached to the transaction |
tags | string[] | no | Up to 10 labels for filtering |
testOutcome | "success", "fail", or a Nylon failure code | no | Force the sandbox result while testing. Omit it and the sandbox picks success about 70% of the time. Sandbox keys only |
prompt | "pin" | no | Keep the sandbox payout pending until you enter the customer PIN. See Sandbox. Sandbox keys only |
curl -s https://api.nylonpay.com/api/services \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"intent": "execute",
"service": "rest",
"action": "make-payout",
"payload": {
"amount": 5000,
"currency": "UGX",
"description": "Withdrawal 1042",
"customer": {
"name": "Jane Doe",
"phoneNumber": "+256700000000"
},
"destination": {
"accountHolderName": "Jane Doe",
"accountNumber": "+256700000000",
"phone": "+256700000000"
},
"reference": "07808afc-9ba0-4dc9-9689-f577a979e1e2"
}
}'A new reference starts a new payout. Reuse the same reference when retrying.
Get payment status
get-status checks one transaction by reference. Use it to confirm a payment after a timeout or when a webhook has not arrived.
| Field | Type | Required | Description |
|---|---|---|---|
reference | string | yes | The reference used at creation |
curl -s https://api.nylonpay.com/api/services \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"intent": "execute",
"service": "rest",
"action": "get-status",
"payload": {
"reference": "11db1c21-7f43-490c-b76e-485baa326edf"
}
}'| Field | Description |
|---|---|
id | Nylon transaction ID |
operatorTid | ID on the customer's operator or bank receipt. null until it is reported |
failureReason | Human sentence when the payment failed or was cancelled, otherwise null |
failureCategory | Broad class: provider, customer, internal, validation |
failureCode | Nylon code such as insufficient_balance |
statusText | Extra human sentence. For on_hold, the review sentence for example "This payout is being reviewed and will complete shortly." |
delayed | true when the payment has been waiting longer than a few minutes |
Get a transaction
get-transaction returns the full transaction record. Pass id or reference, one of the two.
curl -s https://api.nylonpay.com/api/services \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"intent": "execute",
"service": "rest",
"action": "get-transaction",
"payload": {
"id": "cc0e59d2-0a28-4bb6-8344-32aea6f8ce8c"
}
}'List transactions
list-transactions returns your transactions, newest first.
| Field | Type | Required | Description |
|---|---|---|---|
tags | string[] | no | Return transactions carrying all of these tags |
status | string | no | pending, processing, on_hold, successful, failed, or cancelled |
type | string | no | Transaction type filter |
limit | number | no | Page size from 1 to 100. Default 20 |
offset | number | no | Rows to skip. Default 0 |
createdAfter | string | no | ISO 8601 datetime |
createdBefore | string | no | ISO 8601 datetime |
curl -s https://api.nylonpay.com/api/services \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"intent": "execute",
"service": "rest",
"action": "list-transactions",
"payload": {
"limit": 20,
"offset": 0
}
}'Verify a phone number
verify-phone asks the operator for the name registered on a phone number. Use it to confirm a customer's identity before a collection or a payout.
| Field | Type | Required | Description |
|---|---|---|---|
phoneNumber | string | yes | Phone number in any common format, auto-normalized |
purpose | string | no | "collection" or "payout" |
curl -s https://api.nylonpay.com/api/services \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"intent": "execute",
"service": "rest",
"action": "verify-phone",
"payload": {
"phoneNumber": "+256700000000"
}
}'4. Statuses and polling
| Status | Meaning |
|---|---|
pending | Created. The customer has not acted yet, or the payout is queued |
processing | In flight with the operator or bank |
on_hold | Waiting on a review. statusText carries the sentence to show |
successful | Money moved |
failed | It did not complete. failureReason and failureCode say why |
cancelled | Stopped before completion |
A payment in pending or processing for longer than a few minutes comes back with delayed: true on status checks.
Webhooks push status changes to your server. Use get-status or get-transaction to confirm after a timeout, after a delayed result, or when a webhook has not arrived. Space status checks as our polling guidance describes.
5. Errors
Refusals raised by the operation end with an error-type value. Branch on that value, not on message text. A request with missing or malformed fields is refused before the operation runs, with a Validation failed: message naming the field.
error-type | Meaning | Retry |
|---|---|---|
auth | Key, token, or permission refused | No. Get a new token and retry once, then fix the key |
validation | Input rejected | No. Correct the input first |
limit | Account or KYC transaction limit exceeded | No. The amount exceeds the account limit |
rate_limit | Too many requests | Yes, after the wait given in the message |
account | Merchant account missing or not active | No |
provider | The payment provider rejected the operation | Yes, after a delay |
duplicate | The reference belongs to another transaction | No. Retry with a new reference |
not_found | The reference or id does not exist yet | No |
internal | Unexpected server-side failure | Yes, after a delay. Contact support if it repeats |
6. Sandbox
With a sandbox key, pass testOutcome to force the result. Use "success" to walk the happy path and "fail" or a code such as "insufficient_balance" to walk the failure path, including webhooks. Forced fails are labelled Sandbox simulated:.
curl -s https://api.nylonpay.com/api/services \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"intent": "execute",
"service": "rest",
"action": "collect-payment",
"payload": {
"amount": 50000,
"currency": "UGX",
"description": "Order #12345",
"customer": { "name": "John Doe", "phoneNumber": "+256700000000" },
"reference": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"testOutcome": "fail"
}
}'Pass prompt: "pin" to hold a sandbox payment until you enter the customer PIN on the dashboard Sandbox page. Pass both and the forced outcome applies and prompt is ignored. The PIN flow works the same as it does with the SDK; see Testing the customer PIN.
Sending testOutcome or prompt with a live key is refused as a validation error, so remove them before going live.
7. Webhooks
We send a webhook to your server when a payment changes status. The same events, payloads, and signature verification apply whether the payment came through the SDK or the REST API. See Webhooks.
8. Limitations
The REST API covers the core payment flow. The SDKs cover more, and do more of the work for you.
| SDK | REST API | |
|---|---|---|
| Request protection | Every request is signed, and a captured request cannot be sent again | Anyone holding the access token can call with it until it expires |
| Response protection | Every response is verified as coming from us, unchanged | Your code trusts what arrives over HTTPS |
| Signing in | Automatic | You exchange keys for a token and get a new one every hour |
| Waiting for the result | wait() and payment events report the final status | You poll get-status or handle webhooks yourself |
| Retries | Built in, with the same reference | You retry, and must reuse the same reference |
| Errors | Typed errors with a reason to branch on | You read error-type from the message text |
| Keys | Any server | One key per server |
| Operations | Everything on this page, plus invoices, collect-and-wait, payout-and-wait, airtime and bills | The seven operations on this page |
Use the REST API when your stack has no SDK. Otherwise use an SDK: TypeScript, Python or PHP.
See Also
- Collect Payment, the SDK version of
collect-payment - Make Payout, the SDK version of
make-payout - Get Status, status fields and polling guidance
- Security, protect your keys and tokens
- Webhooks, receive status updates