Products
Refunds
Send money from an inflow back to the person who paid it, for example a customer who overpaid or paid by mistake. An inflow is a payment someone made into one of your accounts; Multigate told you about it with the account / inflow webhook.
Before you start
-
MG_BASE_URLandMG_API_KEYare set, as in the Quickstart. - Your API user has the initiate payment or approve payment permission.
- You stored the inflow webhook (Collections). From it you need:
transactionId(IN-…): which inflow to refund;sender: where the money goes back to;Account Number sender: the name on that account.Account Name
- You know the sender's bank code. The inflow webhook doesn't include it (its
bankCodeis your receiving bank), so ask the payer for their bank, and take itsbankCodefrom List banks. - The account that received the inflow still holds at least the refund amount.
Rules
- Which inflows: your own
completedinflows in NGN or GHS, identified by the inflow webhook'stransactionId(IN-…). - How much: all of it, or part with
amount. One refund per inflow: while one is processing or after one completed, another is refused (409). After a partial refund, the rest can't be refunded through the API, so refund the full amount you intend in one go. Afailedrefund doesn't count; you can try again. - To whom: the original sender. When the inflow recorded the sender's account number,
beneficiary.must be that number ("Beneficiary mismatch" otherwise). The sender's bank must be one Multigate can pay refunds to in that currency.account Number - From where: the account that received the inflow. Its available balance must cover the refund when you call, and again when the refund is paid, when the amount is reserved.
- Known issue: inflows credited to a pool account (auto-sweep, dynamic accounts, receivables) can't be refunded today: the check reads the receiving account, which holds nothing. Contact Multigate to return such a payment.
- Fees: no refund fee. The original collection fee isn't returned.
- Approval and screening: refunds don't wait for an approver, but they are screened like payouts.
- Permission: initiate payment or approve payment.
How it works
sequenceDiagram
autonumber
participant You as Your backend
participant MG as Multigate
participant Bank as Sender's bank
Note over You: You received account / inflow with transactionId IN-…
You->>MG: POST /transaction/refund (transactionId, uniqueRequestId, beneficiary)
MG->>MG: Check eligibility, balance and sender
MG-->>You: 200, status processing
MG->>MG: Screening, then reserve the amount
MG->>Bank: Pay the sender
Bank-->>MG: Result
MG->>MG: Debit your account (on success)
MG-)You: transfer / refund (completed or failed)
Refund an inflow
Generate a new unique for the refund and store it, then send the request. Use the inflow webhook's transactionId exactly as received, and the sender's account from the same webhook.
curl -X POST "$MG_BASE_URL/payment-service/api/v1/transaction/refund" \
-H "mp-api-key: $MG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transactionId": "IN-261010-1204557781",
"uniqueRequestId": "rf_20261010_0001",
"amount": 5000,
"beneficiary": { "accountName": "KOLA STORES LTD", "accountNumber": "2093847561", "bankBicOrCode": "011" }
}'
{
"message": "Successful",
"transactionId": "IN-261010-1204557781",
"uniqueRequestId": "rf_20261010_0001",
"currency": "NGN",
"amount": 5000,
"status": "processing"
}
amountmust be a JSON number with up to two decimals, in major units (5000.50). Leave it out for a full refund.- The response always says
processing, and itstransactionIdis the inflow's ID. Track the refund by yourunique.Request Id - The outcome arrives as the
transfer/refundwebhook, with the inflow'stransactionId, yourunique,Request Id amountandstatus(completedorfailed). - If you get no webhook within an hour, look the refund up by your
unique(see the caution below).Request Id - A timeout or
5xxmeans the outcome is unknown. Look the refund up byuniquebefore you retry; a new request for the same inflow is refused withRequest Id 409while the first one is processing or completed, so a retry can't refund twice.
| Field | Where it comes from |
|---|---|
transactionId |
transactionId of the account / inflow webhook (IN-…). |
unique |
You generate it. Unique across Multigate. |
amount |
Optional. Leave it out to refund the whole inflow. |
beneficiary. |
sender of the inflow webhook. |
beneficiary. |
sender of the inflow webhook. |
beneficiary. |
The sender's bank code, from the payer and List banks. Not in the webhook. |
Send the inflow's transactionId (IN-…) character for character. Don't send the bank's payment or a shortened ID.
Refund statuses
| Status | Final | Webhook | Meaning |
|---|---|---|---|
pending |
No | — | Held for screening. |
processing |
No | — | Being paid. Also while Multigate reconciles a refund whose outcome at the bank is unclear, for example one the bank accepted and later reported failed; another refund of the same inflow gets 409 "Refund currently processing" meanwhile. |
completed |
Yes | refund |
The sender was paid and your account debited. |
failed |
Yes | refund (not always) |
Not paid; your account isn't debited. |
stateDiagram-v2
direction LR
[*] --> pending: screening
[*] --> processing
pending --> processing
processing --> completed
processing --> failed
pending --> failed
If the balance can't be reserved when the refund is about to be paid (for example you spent the money after calling), the refund ends failed and no refund webhook is sent. If you don't get a webhook, look the refund up with Find transfers ?unique.
Errors
A 4xx means nothing was created.
| Message | Status | Cause | What to do |
|---|---|---|---|
| Transaction not found | 404 |
Unknown transactionId, not yours, or not an inflow. |
Use the inflow webhook's transactionId exactly. |
| Refunds are not supported for this transaction currency | 400 |
Only NGN and GHS. | Refund outside the API. |
| Only a completed collection can be refunded | 400 |
The inflow isn't completed. | Wait until it is. |
| Refund currently processing / Refund completed already | 409 |
This inflow already has a refund. | Wait for its refund webhook. |
| The requested refund amount cannot be greater than the original transaction amount | 400 |
amount is too high. |
Send less. |
| Invalid amount | 400 |
amount is zero or negative. |
Send a positive amount. |
| Insufficient balance in source account | 400 |
The receiving account can't cover it. | Fund it and retry. |
| Could Not Retrieve Account Balance | 400 |
The balance couldn't be read. | Retry. |
| Beneficiary mismatch | 400 |
The account isn't the original sender's. | Use the sender's account from the inflow webhook. |
| Refunds are not currently configured for this beneficiary's bank | 400 |
Multigate can't pay refunds to the sender's bank in this currency. | Contact Multigate. |
| The original transaction is missing the sender's bank details required to process a refund | 400 |
The inflow record is incomplete. | Contact Multigate support. |
| Unique request ID already exist | 400 |
Reused unique. |
Use a new ID; after a failed refund, retry with a new one too. |
Validation error (amount must be a number) |
400 |
amount sent as a string, or with more than two decimals. |
Send a JSON number. |
Next
- Collections for the inflow webhook and its
transactionId. - Event catalogue for the
refundwebhook.