MoniWave

API reference

Generated from the OpenAPI document, so it cannot drift from the deployed routes. Samples are generated from the same document — a new request field appears in all three languages at once.

Machine-readable spec

OpenAPI v1 — import into Postman or Insomnia, or point a code generator at it.

/docs/openapi.json

Postman

Generated from the same document, so the requests match the routes above. Import the collection and one environment, set apiKey to your sk_test_ key, and run the folders in order — folder 1 creates the collection that folder 3 refunds. Each request captures what the next one needs, so nothing has to be copied by hand.

One environment per base URL above. They ship with apiKey empty and marked secret — a key in a file that gets shared is a leaked key.

Base URLs

https://api-dev.moni-wave.comSandbox (preview) — deterministic simulator, sk_test_ keys
https://api-sandbox.moni-wave.comSandbox at general availability — not yet serving
http://localhost:6003Local development

Every route needs Authorization: Bearer sk_… unless marked Session token. Those three are the hosted-checkout routes: called by the payment page and authorised by the token in the path, never by a key — which is why a secret key must never reach a browser.

Hosted checkout

POST/v1/checkout-sessions

Create a checkout session

Creates a payment page and returns its URL. Use this when you do NOT have the customer's number — a payment link in a chat, a "Pay now" button, an invoice email. If you already know the number, call `POST /v1/collections` and skip checkout.

The amount is fixed here, by you. The returned token authorises paying this one bill and nothing else, so a leaked link risks someone paying a bill that was already owed — not minting a new charge.

You learn the outcome the same way as any other payment: the webhook for the resulting collection, or by polling it. The page is not your notification channel.

Requires an `Idempotency-Key` header.

Request body

amountinteger | string (int64)required
currencystringrequired
descriptionstringrequired
externalIdstringoptional
returnUrlstringoptional
Responses: 201 CheckoutSessionCreatedResponse · 400 ApiErrorResponse · 401 ApiErrorResponse · 409 ApiErrorResponseGuide: Hosted checkout
curl -X POST https://api-dev.moni-wave.com/v1/checkout-sessions \
  -H "Authorization: Bearer $MONIWAVE_SECRET_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
       "amount": 5000,
       "currency": "XAF",
       "description": "Order #1042",
       "externalId": "order-1042",
       "returnUrl": "https://shop.example.cm/orders/1042"
     }'
GET/v1/checkout/{token}Session token

Read a checkout session

Called by the payment page to render itself, and polled to watch the outcome. Authorised by the token in the path — no key, which is why this route is anonymous.

Returns only what the page must display: merchant display name, amount, currency, description, return URL and status. Never the merchant id, never anything about other transactions.

An unknown, malformed or expired token all answer 404 identically. This URL is guessable by anyone on the internet, so it must not reveal whether a session ever existed.

Responses: 200 CheckoutSessionView · 404 ApiErrorResponseGuide: Hosted checkout
curl -X GET https://api-dev.moni-wave.com/v1/checkout/r4dtalicG0OMSxXfZkd4d7LoVkYSphtS
POST/v1/checkout/{token}/paySession token

Pay a checkout session

Submits the customer's number and starts the charge. Anonymous — the token in the path is the entire authorisation.

Returns **202 Accepted**, not 200: the operator has prompted the customer and they have not approved yet. Poll `GET /v1/checkout/{token}` for the outcome.

One session yields at most one successful payment. Concurrent attempts are serialised, so a double-tap cannot produce two charges — but a FAILED attempt may be retried on the same link, which is what lets a customer who mistyped their number try again.

No `Idempotency-Key`: a browser has no secret and no stable identity, so the session itself is the idempotency scope.

Request body

msisdnstringrequired
Responses: 202 PayCheckoutSessionAcceptedResponse · 400 ApiErrorResponse · 404 ApiErrorResponseGuide: Hosted checkout
curl -X POST https://api-dev.moni-wave.com/v1/checkout/r4dtalicG0OMSxXfZkd4d7LoVkYSphtS/pay \
  -H "Content-Type: application/json" \
  -d '{
       "msisdn": "237690000001"
     }'

Collections

POST/v1/collections

Create a collection

Charges a customer's mobile wallet. Returns immediately with status `accepted` — the customer still has to approve on their phone, so this is not a completed payment. Poll the collection or wait for the webhook.

Requires an `Idempotency-Key` header (a UUID you generate). Retrying with the same key replays the original response instead of charging twice.

Request body

amountinteger | string (int64)required
currencystringrequired
descriptionstringrequired
externalIdstringoptional
msisdnstringrequired
Responses: 201 CollectionResponse · 400 ApiErrorResponse · 401 ApiErrorResponse · 409 ApiErrorResponseGuide: Collections
curl -X POST https://api-dev.moni-wave.com/v1/collections \
  -H "Authorization: Bearer $MONIWAVE_SECRET_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
       "amount": 5000,
       "currency": "XAF",
       "description": "Order #1042",
       "externalId": "order-1042",
       "msisdn": "237690000001"
     }'
GET/v1/collections/{id}

Retrieve a collection

The authoritative status. Webhooks are a convenience; this is the source of truth, and a client that only listens for callbacks will eventually miss one.

Another merchant's collection returns 404, identical to an id that does not exist — a 403 would confirm it exists.

Responses: 200 CollectionResponse · 401 ApiErrorResponse · 404 ApiErrorResponseGuide: Collections
curl -X GET https://api-dev.moni-wave.com/v1/collections/col_019fff54dbf37614b3420ade5ab24ae1 \
  -H "Authorization: Bearer $MONIWAVE_SECRET_KEY"

Payouts

POST/v1/payouts

Create a payout

Sends money to a mobile wallet. Two different balances can refuse it: your own spendable balance, checked here (`payout_exceeds_balance` — the amount plus its fee must fit); and our operator float, checked at submission, which FAILS the payout with `insufficient_float` rather than rejecting the call.

A payout you requested seconds ago already holds its amount, even though your posted balance has not moved — the ledger only posts on completion.

Requires an `Idempotency-Key` header.

Request body

amountinteger | string (int64)required
currencystringrequired
descriptionstringrequired
externalIdstringoptional
msisdnstringrequired
Responses: 201 PayoutResponse · 400 ApiErrorResponse · 401 ApiErrorResponse · 409 ApiErrorResponseGuide: Payouts
curl -X POST https://api-dev.moni-wave.com/v1/payouts \
  -H "Authorization: Bearer $MONIWAVE_SECRET_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
       "amount": 5000,
       "currency": "XAF",
       "description": "Order #1042",
       "externalId": "order-1042",
       "msisdn": "237690000001"
     }'
GET/v1/payouts/{id}

Retrieve a payout

`completed` means the recipient has the money. Another merchant's payout returns 404, identical to an id that does not exist.

Responses: 200 PayoutResponse · 401 ApiErrorResponse · 404 ApiErrorResponseGuide: Payouts
curl -X GET https://api-dev.moni-wave.com/v1/payouts/col_019fff54dbf37614b3420ade5ab24ae1 \
  -H "Authorization: Bearer $MONIWAVE_SECRET_KEY"

Refunds

POST/v1/refunds

Refund a collection

A refund is its own transaction with its own `ref_` id — never a state change on the original collection, which stays `completed` forever.

Only `completed` collections are refundable. Omit `amount` to refund everything still refundable; partial refunds are allowed and accumulate, and a refund still in flight holds its amount, so `refund_exceeds_refundable` can appear even when the arithmetic looks fine.

An unknown collection, or one belonging to another merchant, returns 404.

Request body

amountinteger | string (int64)optional
collectionIdstringrequired
descriptionstringoptional
Responses: 201 RefundResponse · 400 ApiErrorResponse · 401 ApiErrorResponse · 404 ApiErrorResponse · 409 ApiErrorResponseGuide: Refunds
curl -X POST https://api-dev.moni-wave.com/v1/refunds \
  -H "Authorization: Bearer $MONIWAVE_SECRET_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
       "amount": 5000,
       "collectionId": "col_019fff54dbf37614b3420ade5ab24ae1",
       "description": "Order #1042"
     }'
GET/v1/refunds/{id}

Retrieve a refund

Another merchant's refund returns 404, identical to a missing id.

Responses: 200 RefundResponse · 401 ApiErrorResponse · 404 ApiErrorResponseGuide: Refunds
curl -X GET https://api-dev.moni-wave.com/v1/refunds/col_019fff54dbf37614b3420ade5ab24ae1 \
  -H "Authorization: Bearer $MONIWAVE_SECRET_KEY"