MoniWave

Refunds

A refund returns money from your balance to the customer who paid, linked to the original collection.

A refund is its own transaction with its own lifecycle — it never changes the original collection's status. A fully refunded collection stays completed; the refund appears alongside it. This keeps history honest: you can always see that a payment happened and that it was returned.

Create a refund

POST /v1/refunds

Requires Authorization and Idempotency-Key.

Request

FieldTypeRequiredNotes
collectionIdstringyesThe col_… id being refunded.
amountintegernoMinor units. Omit for a full refund of everything still refundable.
descriptionstringnoDefaults to Refund of col_….
# Full refund
curl -X POST https://api-dev.moni-wave.com/v1/refunds \
  -H "Authorization: Bearer sk_test_…" \
  -H "Idempotency-Key: b7d3f9a1-2c8e-4f5b-a0d6-3e9c1b4a7f28" \
  -H "Content-Type: application/json" \
  -d '{ "collectionId": "col_019fff54dbf37614b3420ade5ab24ae1" }'

# Partial refund
  -d '{ "collectionId": "col_019fff…", "amount": 2000, "description": "Damaged item" }'

Response — 201 Created

{
  "id": "ref_019fff8b3d295cf0b1824ae9d3f6c521",
  "status": "accepted",
  "collectionId": "col_019fff54dbf37614b3420ade5ab24ae1",
  "amount": 5000,
  "currency": "XAF",
  "msisdn": "237690000001",
  "description": "Refund of col_019fff54dbf37614b3420ade5ab24ae1",
  "failureReasonCode": null,
  "createdAt": "2026-08-14T10:02:44.771Z"
}

Refund ids are prefixed ref_. The currency and recipient MSISDN are inherited from the original collection — you cannot refund to a different number.

Retrieve a refund

GET /v1/refunds/{id}

What can be refunded

The original collection must be completed. A collection that is still pending, failed, or expired never took money, so there is nothing to return — attempting it returns not_refundable.

Partial refunds and the remaining balance

You can refund a collection in several parts, as long as the total never exceeds the original amount.

Important: the remaining refundable amount counts refunds in any non-final-failure status — including ones still accepted or submitted. An in-flight refund holds its amount.

Worked example on a 10 000 XAF collection:

StepRequestResultRemaining
1Refund 6 000201accepted4 000
2Refund 5 000400 refund_exceeds_refundable4 000
3Refund 4 000201accepted0
4Refund anything400 refund_exceeds_refundable0

At step 2 the first refund had not completed yet — but its 6 000 was already reserved. Without that rule, two concurrent refunds could each pass a naive check and together return more than was ever collected.

If a refund ends up failed or expired, its amount is released back into the refundable remainder automatically.

The error message states the actual remainder, so you can correct and retry:

{
  "error": {
    "code": "refund_exceeds_refundable",
    "message": "Refundable remainder is 4000 XAF (6000 already reserved by other refunds).",
    "details": []
  }
}

Fees

Refunds carry no fee, but the fee on the original collection is not returned — it was earned on the original transaction. Refunding a 10 000 XAF collection that cost 200 XAF in fees debits 10 000 XAF from your balance; the 200 XAF stays spent.

So on a completed refund, fee is 0 and net equals amount:

{ "amount": 10000, "currency": "XAF", "fee": 0, "net": 10000, "status": "completed" }

Both are null until the refund is completed. Note the asymmetry this makes visible: the collection credited you net 9 800 and the refund debits net 10 000, so a collect-then-fully-refund cycle leaves you 200 XAF down.

Statuses

Same lifecycle as any transaction: acceptedsubmittedcompleted / failed / expired. completed means the customer has the money back. On failed, failureReasonCode carries the stable reason (errors.md).

Errors

CodeHTTPMeaning
invalid_collection_id400Not a valid transaction id.
not_a_collection400The id is a payout or refund, not a collection.
not_refundable400The collection is not completed.
refund_exceeds_refundable400Amount exceeds the remainder (message states it).
invalid_amount400Not positive, or nothing left to refund.
404Collection does not exist, or belongs to another merchant.