Skip to content

Refunds

Refund all or part of a confirmed payment, choose who pays the fee and set the project default.

A refund sends money back to the customer, on the mobile number that paid. It is taken from the merchant's available balance. It happens in two steps: a quote (amounts frozen, no money movement), then the refund.

Conditions

  • The payment is confirmed.
  • The payment was created with customer_phone: without the customer number, no refund is possible.
  • The amount does not exceed what is left to refund (payment amount minus refunds done or in progress). Partial refunds are allowed. Beyond that: 400 INVALID_REQUEST with fields.amount, which gives the refundable remainder.
  • The available balance covers the debited amount (merchant_debited).

No identity check is required to refund.

1. Get a quote

Terminal
curl -X POST "$API_URL/v1/refund-quotes" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_id": "8d1f0c6e-3b7a-4c55-9a51-2f0e5c1d7a90",
    "amount": "5000",
    "fee_payer": "merchant"
  }'
201 · application/json
{
  "payment_id": "8d1f0c6e-3b7a-4c55-9a51-2f0e5c1d7a90",
  "amount": "5000",
  "fee": "…",
  "customer_receives": "5000",
  "merchant_debited": "…",
  "fee_payer": "merchant",
  "quote_hash": "a3f9…",
  "expires_at": "2026-10-07T10:02:00Z"
}

The quote expires after 2 minutes. Show it before confirming: it gives the exact amounts.

Who pays the fee: fee_payer

fee_payerThe customer receivesThe merchant is debited
merchantamountamount + fee
customeramount − feeamount

Without fee_payer, the project default applies.

2. Refund

Reuse the quote values, add reason and an Idempotency-Key:

Terminal
curl -X POST "$API_URL/v1/refunds" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: refund-order-1042" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_id": "8d1f0c6e-3b7a-4c55-9a51-2f0e5c1d7a90",
    "amount": "5000",
    "fee_payer": "merchant",
    "reason": "customer_request",
    "quote_hash": "a3f9…"
  }'
reasonMeaning
customer_requestCustomer request.
duplicateDuplicate payment.
product_not_deliveredProduct or service not delivered.
other_documentedOther documented reason.

A quote is single use. Expired: 409 QUOTE_EXPIRED. Unknown, already used or not matching the request (another amount, another fee_payer): 409 QUOTE_INVALID. The message is also in fields.quote_hash. Request a new quote. IDEMPOTENCY_CONFLICT now only means the Idempotency-Key reused with another body.

Response: 201 on creation, 200 for a request replayed with the same key and the same body.

201 · application/json
{
  "id": "c27e5a10-6f8b-4d2c-b1e3-7a9d0f4c8e21",
  "payment_id": "8d1f0c6e-3b7a-4c55-9a51-2f0e5c1d7a90",
  "status": "processing",
  "amount": "5000",
  "currency": "XOF",
  "fee": "…",
  "customer_receives": "5000",
  "merchant_debited": "…",
  "fee_payer": "merchant",
  "reason": "customer_request",
  "created_at": "2026-10-07T10:01:00Z",
  "updated_at": "2026-10-07T10:01:01Z"
}

Statuses

StatusMeaningWebhook event
requestedRefund recorded, funds being reserved.refund.requested
processingPayment to the customer in progress.None
succeededThe customer received the amount.refund.succeeded
failedPayment declined; the reserved funds return to the available balance.refund.failed
unknownUncertain outcome; the funds stay reserved until the operator answers, never released by default.None

Read and list

Terminal
curl "$API_URL/v1/refunds/$REFUND_ID" -H "Authorization: Bearer $API_KEY"
curl "$API_URL/v1/refunds?payment_id=$PAYMENT_ID" -H "Authorization: Bearer $API_KEY"

Filters: payment_id, status, from, to, limit, offset. GET /v1/payments/{id} also lists the payment's refunds in refunds.

Project default

Terminal
curl "$API_URL/v1/settings" -H "Authorization: Bearer $API_KEY"

curl -X PUT "$API_URL/v1/settings" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"refund_fee_payer_default": "customer"}'

refund_fee_payer_default is merchant or customer. Every change is logged. The same setting is available in the dashboard, Refunds section.

Refund fee rate: see Fees.