- Distributed partial refunds — return a refund from multiple internal accounts in a single operation
- Unblock — recover a refund or transfer that is stuck in
PROCESSING
Distributed partial refunds
When a received Pix is distributed internally across several accounts — for example, the principal amount to the customer’s account and a fee to a fee account — a later MED/fraud refund may need to be debited partially from each of those accounts. The standard refund flow debits from a single account. To split the debit, send an optional
operations array in the request body. The presence of operations is the only signal — there is no new endpoint, env var, or feature flag.
Request
- Without
operations→ the current single-account flow runs unchanged. - With
operations→ the plugin debits eachaccountAliasfor itsamountin Midaz, and BTG receives a single pacs.004 for the total refund value.
Validation rules
| Rule | Error |
|---|---|
sum(operations[].amount) must equal amount | 400 OPERATIONS_SUM_MISMATCH |
accountAlias must not repeat within the request | 400 DUPLICATE_ACCOUNT_ALIAS |
each amount must be greater than 0 | 400 |
each amount must have at most 2 decimal places | 400 |
each accountAlias must have participated in the original transaction | 422 ACCOUNT_ALIAS_NOT_IN_ORIGINAL_TRANSACTION |
| the original transaction must exist in Midaz | 422 ORIGINAL_TRANSACTION_NOT_FOUND |
The endpoint, BTG flow (pacs.004 with the total value), idempotency, and authentication are identical to the standard refund. Only the internal Midaz debit composition changes.
Example — Cappta
A R 49,000.00 to the customer account and R 1,000.82, but only R 0.82 from the customer account and R 1,000.82 refund — no manual ledger consolidation required.
Unblocking stuck operations
A refund or transfer whose reversal call to BTG times out before confirmation can remain stuck in
PROCESSING, with the hold still applied in Midaz. Two dedicated endpoints re-query BTG and drive the operation to its correct terminal state.
| Method | Endpoint | Unblocks |
|---|---|---|
POST | /v1/refunds/{refund_id}/unblock | A refund stuck in PROCESSING |
POST | /v1/transfers/{transfer_id}/unblock | A transfer stuck in PENDING/PROCESSING |
X-Account-Id header.
How unblock works
The plugin re-queries the reversal/transfer status at BTG and:
- If BTG reports
CONFIRMEDorERROR→ dispatches the corresponding settlement and moves the operation to its terminal state. - If BTG still reports
INITIATED/PROCESSING→ returns HTTP 200 with no action; retry later.
entity, returnIdentification, or originalEndToEndId diverge from the local record, preventing settlement against the wrong transaction.
refund, a message, and the btgStatus.
Handling a 404 from BTG
When BTG no longer has the reversal/transfer (returns
404), behavior depends on the operation type and the opt-in allowNotFoundUnblock flag in the request body:
| Operation | Default (strict) | With allowNotFoundUnblock: true |
|---|---|---|
Transfer (/v1/transfers/{id}/unblock) | Returns an error | Reverts the Midaz hold (best-effort, idempotent), marks the cashout FAILED with BTG_NOT_FOUND, emits a CASHOUT/TRANSFER outbound webhook, and returns 200 with btgStatus: "NOT_FOUND" |
Refund (/v1/refunds/{id}/unblock) | Returns PIX-1012 (ErrProviderRefundNotFound) | Reverts the hold and drives the refund to its terminal state |
The opt-in 404 recovery applies only to
CASHOUT operations in PENDING/PROCESSING with a non-empty endToEndId. Cash-ins and terminal statuses never enter this branch.Intra-PSP limitation
The transfer unblock flow queries BTG for status, which does not apply to intra-PSP (internal) transfers — there is no BTG transaction. Calling unblock on an intra-PSP transfer returns a synchronous error (
PIX-0418). Proper intra-PSP unblock logic is a future enhancement. See Intra-PSP transfers.
Next steps
- Intra-PSP transfers — Internal P2P transfers and refunds
- MED 2.0 — Funds Recovery — Cross-account fraud recovery
- Webhooks — Refund and transfer event handling
- API reference — Full API documentation

