Skip to content
MultigateAPI
Support Staff

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_URL and MG_API_KEY are 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;
    • senderAccountNumber: where the money goes back to;
    • senderAccountName: the name on that account.
  • You know the sender's bank code. The inflow webhook doesn't include it (its bankCode is your receiving bank), so ask the payer for their bank, and take its bankCode from List banks.
  • The account that received the inflow still holds at least the refund amount.

Rules

  • Which inflows: your own completed inflows in NGN or GHS, identified by the inflow webhook's transactionId (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. A failed refund doesn't count; you can try again.
  • To whom: the original sender. When the inflow recorded the sender's account number, beneficiary.accountNumber must be that number ("Beneficiary mismatch" otherwise). The sender's bank must be one Multigate can pay refunds to in that currency.
  • 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 uniqueRequestId 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"
}
  • amount must 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 its transactionId is the inflow's ID. Track the refund by your uniqueRequestId.
  • The outcome arrives as the transfer / refund webhook, with the inflow's transactionId, your uniqueRequestId, amount and status (completed or failed).
  • If you get no webhook within an hour, look the refund up by your uniqueRequestId (see the caution below).
  • A timeout or 5xx means the outcome is unknown. Look the refund up by uniqueRequestId before you retry; a new request for the same inflow is refused with 409 while 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-…).
uniqueRequestId You generate it. Unique across Multigate.
amount Optional. Leave it out to refund the whole inflow.
beneficiary.accountNumber senderAccountNumber of the inflow webhook.
beneficiary.accountName senderAccountName of the inflow webhook.
beneficiary.bankBicOrCode The sender's bank code, from the payer and List banks. Not in the webhook.
Use the exact inflow transactionId

Send the inflow's transactionId (IN-…) character for character. Don't send the bank's paymentReference 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
A failed refund can arrive without a webhook

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 ?uniqueRequestId=.

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 uniqueRequestId. 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