Freeze the payout for review

Create a draft with amount, currency, source account, recipient, and purpose. Show all fields to the approver. Save an immutable approval version or digest and reject execution if any approved field changes.

Enforce distinct roles on the server

Check that the requester and approver are different identities. Apply role limits and payout thresholds at the API, including batch files and scheduled jobs. OWASP transaction authorization guidance calls for a clear sequence and validation before execution.

Test bypass routes

Try self approval, a stale approval after an edit, a lower role calling the release endpoint, and two releases at once. Log each decision and provider result. Reconcile released payouts to the ledger and bank result.

Approval record

Make approval a separate record with requester, approver, version, time, and approved fields. Require an independent actor for release when the risk threshold calls for it. Recheck current role and employment status at approval and release time. Batch payouts need approval of the full file digest and a clear rule for changed rows.

Check indirect release paths

Map every way a payout reaches the provider: direct API, scheduled worker, batch import, staff tool, and replay queue. Each path must call the same release policy or a policy with the same preconditions. A permission test on the front-end approval button leaves worker and batch routes exposed. Denied release attempts should leave no provider request and no ledger debit. Store requestor, approver, policy version, and provider reference so a reviewer can verify separation of duties after settlement.

Handle a partially accepted batch

Use PAY-12 exactly as supplied: approve three recipients, then swap one recipient without changing the total. Release must fail before any provider call. Define one canonical encoding for approval fields so a row order change, currency change, new recipient, or changed fee rule has a clear policy outcome. Freeze the batch version and each line’s operation ID. For a whole-batch approval, any material line change ends that approval.

After a valid release, the provider can accept some lines while another line fails or has an unknown result. Keep each line’s approved amount, recipient, provider ID, and state. If line A succeeds and line B times out, retrying the whole batch with new IDs can pay A twice. Resolve B through the provider’s documented lookup and retry path; retain A’s successful result.

Test a crash after the first provider acceptance, then restart the release worker. Successful lines stay complete; unknown lines stay pending; confirmed failures get their own repair decision. A new recipient or amount requires approval again. The final batch total is the sum of its line outcomes, not the state of the last response. Save the approved digest, line decisions, provider IDs, and reconciled journals.

Evidence to retain

Keep the frozen payout fields, version or digest, maker ID, checker ID, approval time, release request, and provider result.

Sources

Put this into practice

Test a single payout and a batch payout. An edit to any approved amount or destination must require a new approval. See our business banking security reviews and payment gateway testing service. To check a live flow, request a security review.