Orange DOCS
Payments

Create and track payments

Creating a payment is asynchronous — you get a payment ID immediately, then poll for status or receive a webhook when it changes. Every payment targets a merchantId, and amount is always a whole number in the destination currency (ZAR, NGN, KES…).

💡
Use webhooks. webhookUrl and webhookSecret are optional but strongly recommended — they replace polling with real-time status updates. See Webhooks.

The payment object

Every endpoint on this page returns or accepts this object. Fields are grouped by how you use them — what you send, what comes back, and the lifecycle the status moves through.

Required
merchantId*
string
Merchant receiving the payment
amount*
integer
Destination currency, whole number > 10
Optional
referenceoptional
string
Required for SA merchants (≤20 alnum chars)
tagoptional
string
Your internal reference ID
typeoptional
enum
PURCHASE · SERVICE · SALARY · BILL · LOAN
fromCountryoptional
string
Source country/currency. Default BTC
isSlowPaymentoptional
boolean
Extended window, market-rate pricing
bufferRateoptional
float
Slippage buffer, 0–0.10. Surplus refunded as change
Returned in responses
id
string
Unique payment identifier
status
enum
Lifecycle state (see Status values →)
statusLog
string
Latest human-readable status message
amountSettled
number
Final amount settled to the recipient
amountDeposited
number
Amount deposited so far
amountBtc
string
BTC amount involved (sats)
btcPrice
string
BTC price used for conversion
fromCurrency
string
Source currency (derived from fromCountry)
destinationCountry
string
Destination country (from the merchant)
destinationCurrency
string
Destination currency (from the merchant)
paymentType
string
Routing type, e.g. WRAPPED_INTERBANK_PAYMENT, ONRAMP
lightningInvoice
JSON str
Lightning invoice details
depositBankDetails
JSON str
Where to deposit (bank-transfer flows)
recipientBankDetails
JSON str
Recipient bank / Lightning details
isInstantPayment
boolean
Whether the payment is processed instantly
validityDuration
integer
Validity window in seconds
otcFees
number
OTC fees charged
bufferAmountSats
number
Buffer reserved from bufferRate (sats)
changeRefundAmount
number
Unused buffer refunded as change (sats)
changeRefundStatus
string
Status of the change refund
meta
JSON str
Extensible metadata — see note below
date
datetime
Payment creation timestamp
createdAt
datetime
Record created timestamp
updatedAt
datetime
Record last-updated timestamp
💡
About meta. A JSON string for extensible metadata. On a request you can set meta.collectionMethod (Kenya funding — MPESA by default, or BANK_TRANSFER). For a sub-payment in a payment group it carries { paymentGroupExecutionId, isMemberPayout, memberIndex, memberName, lnurl? }. Internal keys (e.g. RefundConfig) are stripped from API responses.
payment
{
  "id": "eafdea43-63a7-4f61-b8ae-823c1b3aa236",
  "merchantId": "9f430126-99da-42e8-afb1-4cab619cda5a",
  "amount": 100,
  "amountSettled": 102.28,
  "status": "success",
  "statusLog": "payment completed successfully",
  "fromCountry": "BTC",
  "destinationCountry": "RSA",
  "destinationCurrency": "ZAR",
  "reference": "INV12345",
  "tag": "ORDER-2024-001",
  "type": "PURCHASE",
  "otcFees": 15,
  "date": "2024-09-27T21:01:49.535Z"
}

Transaction limits

🇿🇦

South Africa (ZAR)

Regulatory limits depend on time of day (SARB rules, Africa/Johannesburg time).

Business hours
Weekdays 06:00–15:00
R 1,000,000
Restricted hours
Weekends, holidays, after-hours
R 250,000
Minimum R 10. For SARB compliance, payments above R 50,000 should include supporting documents (see Attach invoice).
🇰🇪

Kenya (KES)

Max depends on the recipient merchant's payout rail. amount is the KES the recipient receives.

M-Pesa
150 – 250,000
Bank (PesaLink)
150 – 1,600,000
💡
For fromCountry, prefer the country name "Kenya" or "KE" ("KES" is also accepted).

Optional parameters

Extra fields you can include on POST /payments. Expand any to see what it does.

Endpoints

6 calls
GET /payments

List all payments

Retrieve a paginated list of your payments. Pass the offsetMarker from one response as the next request's offset. It returns null when there are no more results.

Query parameters
limitoptional
integer
Items per page (default 100, max 100)
offsetoptional
string
Pagination marker from previous response
GET /payments
{
  "items": [
    {
      "id": "eafdea43-63a7-4f61-b8ae-823c1b3aa236",
      "merchantId": "9f430126-99da-42e8-afb1-4cab619cda5a",
      "amount": 10,
      "amountSettled": 10.22,
      "status": "success",
      "destinationCurrency": "ZAR",
      "reference": "test4",
      "type": "PURCHASE",
      "otcFees": 20,
      "date": "2024-09-27T21:01:49.535Z"
    }
  ],
  "offsetMarker": "%7B%22id%22%3A%22e029...%22%7D"
}
GET /payments/:id

Retrieve a payment

Fetch a single payment by its ID — the same shape as the objects in the list endpoint.

💡
isSlowPayment only appears in the response when it was set to true on the request.
GET /payments/:id
{
  "id": "700293cc-5ab7-4869-9f8a-a3f0558e25eb",
  "merchantId": "9f430126-99da-42e8-afb1-4cab619cda5a",
  "amount": 12,
  "amountSettled": 12.28,
  "status": "expired",
  "statusLog": "payment has expired",
  "destinationCurrency": "ZAR",
  "reference": "test1",
  "type": "PURCHASE",
  "otcFees": 15,
  "date": "2024-09-16T19:45:45.750Z"
}
POST /payments

Create a payment

The payload varies by destination country (where the recipient is). Pick one to see its requirements and example.

  • reference is required (≤20 chars, /^[a-zA-Z0-9 ]{1,20}$/).
  • tag optional, for your own tracking.
  • All other optional fields can be omitted.
POST /payments
{
  "merchantId": "9f430126-99da-42e8-afb1-4cab619cda5a",
  "amount": 100,
  "reference": "INV12345",
  "type": "PURCHASE",
  "tag": "ORDER-2024-001"
}
POST /payments

Fiat → Fiat bank transfer

Move money bank-to-bank across borders. Set fromCountry for the source; amount is in the destination currency.

Supported source countries
Nigeria, Kenya, UK, Europe
⚠️
From UK/Europe: references must be exact. Any extra characters cause payment mismatches and slow settlement.
POST /payments
{
  "merchantId": "323b268b-c802-4ac6-86a6-18a69e46ca6a",
  "amount": 50,
  "fromCountry": "Nigeria",
  "reference": "test1",
  "type": "PURCHASE"
}
After processing
Poll the payment (or get a webhook) — depositBankDetails then carries the source-currency account to deposit into.
POST /payments

USDT → Fiat or BTC

Fund a payment with USDT (Tether) on Ethereum. Orange returns a one-time deposit address; once it settles, proceeds route to the merchant's configured off-ramp.

ZARBank transfer (FiveWest)
KESM-Pesa
BTCLightning Network
⚠️
Ethereum (ERC-20) only. USDT sent on Tron, BSC, Solana, Polygon or any other network is permanently lost.
⚠️
Amount is in cents. amount: 151 means send 1.51 USDT. Divide by 100.
POST /payments
{
  "merchantId": "your-merchant-id",
  "amount": 20,
  "fromCountry": "USDT",
  "reference": "INV12345",
  "type": "PURCHASE"
}
POST /payments/:id/document

Attach an invoice

For SARB compliance, payments above 50,000 ZAR should include supporting documents. Attach multiple files by calling once per file — re-uploading never overwrites.

🇿🇦
South Africa only — a SARB requirement. Doesn't apply to other destinations.
Supported types
application/pdf image/png image/jpeg
upload
curl --location \
  'https://api.useorange.com/payments/{id}/document' \
  --header 'Authorization: Bearer YOUR_JWT_TOKEN' \
  --form 'file=@/path/to/invoice.pdf'
GET /payments/:id/documents

List payment documents

Returns every attached document with short-lived presigned URLs you can download directly — no auth header needed on the presigned URL itself.

💡
Presigned URLs expire after 900 s (15 min). Call again for fresh ones.
GET /payments/:id/documents
{
  "items": [
    {
      "paymentId": "590054d6-567e-4329-b7a2-e46577079df0",
      "documentId": "590054d6-...#1777319066904",
      "presignedUrl": "https://...s3...X-Amz-Expires=900...",
      "createdAt": "2026-04-27T19:44:27.244Z"
    }
  ],
  "presignedUrlExpiresInSeconds": 900
}