Skip to main content
Batch Refunds is a tab of the Portal’s Transactions module, next to Payments, Refunds and Reports, and the only place in the Portal where refunds are initiated. Each row of your file becomes an ordinary refund record, so preconditions, partial refunds, cross-currency amounts and returned fees behave exactly as described in Refund FAQ. The tab is enabled per account — if it is not in your Portal, it has not been switched on for you. Inside it, Export Batch Refund Template and Upload Batch Refund File require the batch-refund upload permission, while the list, its filters and Export are open to any role that can open the tab: if you can see the buttons, you can use them.

What does the flow look like end to end?

The whole flow is a Portal feature performed in your browser session; there is no batch endpoint on the API-key-signed integration API — for an integration, use the single refund API instead. Confirming produces a Refund Batch ID shaped BRP + MID + YYYYMMDD + a 6-digit daily sequence, e.g. BRP<your merchant ID>20260225000001; the batch’s Batch Name is the file name you uploaded, so name files something you will recognise later.

What must the file look like?

Hard limits: .csv only, at most 2 MB, at most 2,000 records, non-empty and parseable — the Portal checks extension and size before uploading, the server checks both again. Two things catch people out: delete the sample row (a row carrying the template’s example order id is rejected on its own, before any other check), and one currency per file.

Why was my whole file rejected?

Upload validation is all-or-nothing: nothing is created, and the Portal lists offending rows as row, field, message. Checks run in a fixed order, which is why you often see only one category at a time:
  1. File-level checks — format, size, emptiness, duplicate content, a batch already in progress.
  2. The sample-row check; if it fires, it is the only error returned.
  3. Per-row checks — order exists under your MID, no duplicate order inside the file, amount > 0, currency valid, consistent, and equal to the order’s currency.
  4. The single-refund preconditions per row — order paid and refundable, no refund already in progress, inside the refund window, amount within the remaining refundable amount, no dispute lock (Refund FAQ explains each).
  5. The batch balance check, only if every row passed — the file’s total must be covered by the same refund funding source a single refund would use, or you get one batch-level error with no row number (“Insufficient merchant refund account capacity for batch refund”).
Stablecoin orders (USDT, USDC) cannot go in a batch: they require a refund address, for which the template has no column. Refund those individually.

Why can’t I upload — duplicate file, or a batch in progress?

Duplicate detection works on file content, not name, scoped to your MID. Re-uploading the same content while an earlier upload is still awaiting confirmation returns that same pending upload, so you can re-open the confirm dialog; once a batch has been created from that content, the content is rejected permanently — to retry failed rows build a new file, and any change, even the reason text, makes it a new file. A validated upload also expires if left sitting: confirming later fails with File token is invalid or expired, and you upload again. Separately, upload and confirm both refuse while any refund in any of your batches is still in progress (“You have a batch refund in progress. Please wait for it to complete.”), so plan large runs as a queue of files; single refunds via the API are not blocked by this. And because confirm re-validates every row, a batch left sitting in the confirm dialog while things changed — an order refunded elsewhere, a window closed, the balance moved — fails at confirm with the first offending row’s message instead of creating a partly invalid batch.

What do the batch statuses mean?

Status is derived from the row counts rather than tracked separately, so it moves on its own as rows settle. Total / Success / Failed / Processing Count are shown per batch: Failed Count includes rows that failed validation at submit time, Processing Count rows not yet started. One bad row does not stop the batch — rows are processed independently, which is what PARTIAL_SUCCESS means, and a batch has no retry action: put the failed rows in a new file, or refund them one at a time.

Where do I see which rows failed and why?

Click Detail on a batch: it opens the Refunds tab pre-filtered by that Refund Batch ID, carrying the batch page’s date range. Each refund is then a normal record — its detail page shows the Refund Batch ID, and the failure reason when the status is Refund Failed. The Refunds tab also has a Refund Batch ID column and filter, so you can reach the same view directly. The batch list export (Batch_Refunds_<timestamp>.csv) is a batch-level summary — ids, name, amount, currency, status, the four counts, operator, created and updated times — and does not contain per-row failure reasons; times in it use the UTC offset your browser reports. The list itself filters on Refund Batch ID, status, currency and created datetime, and opens on the last three months. Rarely a row fails so early that no refund record is created: it still counts in Failed Count with nothing to open in the Refunds tab — contact support with the Refund Batch ID for those.

How does this relate to the single refund API?

Same engine, different entry point: identical lifecycle, account selection and settlement treatment. Two practical differences. Refund request id — batch rows get a system-generated one you never see, so track them by Refund Batch ID or by the refund order id in the Portal, not by your own reference. Notifications — batch rows carry no per-row notify URL, so refund notifications go to the default refund notification URL configured for your account; with none configured, batch rows send no webhook and the Portal is your only view.
A file that keeps failing? Contact support with it and, if one was created, the Refund Batch ID.