Use cases
Collections for fintechs and platforms
Give each of your merchants their own named NGN account number, know as soon as their buyers pay, and pay merchants out to their own banks.
Who it's for: fintechs, marketplaces and platforms that collect money on behalf of the businesses they serve. In these docs your merchants are customers (the API calls them sub-clients): businesses you onboard and act for with onBehalfOf.
Architecture
flowchart LR
subgraph mg [Multigate]
a1[(Merchant A wallet<br/>virtual account 9912345678)]
a2[(Merchant B wallet<br/>virtual account 9912345679)]
end
p1([Buyer]) -->|bank transfer| a1
p2([Buyer]) -->|bank transfer| a2
a1 -.->|account / inflow| hook[Your webhook endpoint]
a2 -.->|account / inflow| hook
hook --> ledger[(Your merchant ledger)]
a1 -->|settlement payout, onBehalfOf A| b1([Merchant A's bank])
a2 -->|settlement payout, onBehalfOf B| b2([Merchant B's bank])
Each merchant gets a wallet (a balance in one currency on Multigate's ledger) with a virtual account: a bank account number in the merchant's name, issued by a partner bank, that credits the wallet. Buyers pay that number by ordinary bank transfer.
Before you start
- A sandbox API key, and
MG_BASE_URL,MG_API_KEYandMG_CLIENT_IDset as in the Quickstart. - A webhook URL registered before you open accounts (Webhooks overview), with signature verification.
- Your API user's permissions: manage customers (and approve customers to skip a second approver), manage accounts, manage beneficiaries and initiate payment.
- Multigate has set up an NGN partner bank for you, and you have accepted its terms of service in the dashboard.
- Ask Multigate whether auto-sweep to a pool account is set up for your business. With auto-sweep, every inflow is credited to one pool account of yours instead of the merchant's wallet, which changes how you settle and refund (see below).
Build plan
Create the merchant with Create a customer, add its directors and owners, upload its documents and submit it: follow Customers step by step. Store the returned entityId: it's the customer ID you send as onBehalfOf from now on.
Wait for client / subclient_ and check status: active means approved, rejected means declined (the same event is used for both). In the sandbox, reviews can take days: ask your Multigate contact to complete them.
Once the merchant is active, call Create an account with onBehalfOf set to the customer ID and a new unique:
curl -X POST "$MG_BASE_URL/client-service/api/v1/accounts" \
-H "mp-api-key: $MG_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "uniqueRequestId": "acme_acct_kola_ngn", "currencyIso3Code": "NGN", "accountName": "Kola Stores Collections", "onBehalfOf": "Kx81mQ2pZr4tV9wYa3Bc5" }'
Store account. and every funding against the merchant, and show the merchant their account number. Payers see the merchant's registered business name (funding).
If status is processing, funding is empty: wait for account / account_ (its clientId is the customer ID). An account a bank refuses later sends no webhook, so poll Get an account by ID for accounts that stay processing. See Accounts › When the account isn't active yet.
Every payment sends account / inflow to your webhook URL. The payload has no customer ID, so:
- Verify the signature, then store the
Idempotency-Keyheader. - Skip it if you've already recorded its
transactionId(the same inflow can be delivered twice). - Find the merchant by
receiving(the number the buyer paid into), never byAccount Number accountId. - Parse
amount(a decimal string) as a decimal, and match the order bynarrationor your own order logic. - Store
transactionId(IN-…): you need it for refunds and reconciliation.
Details: Collections › The inflow webhook. In the sandbox, test with Simulate an inflow.
Check the merchant's own bank account with Validate a beneficiary account (NGN), then save it with Create a beneficiary, passing the customer ID in subClientId. Store beneficiary. as the merchant's settlement beneficiaryId. See Beneficiaries for your customers.
Pay each merchant from their own wallet with Create a transfer, with onBehalfOf = the customer ID:
curl -X POST "$MG_BASE_URL/payment-service/api/v1/transfer/single-transfer" \
-H "mp-api-key: $MG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"uniqueRequestId": "acme_settle_kola_20261010",
"reference": "Settlement 10 Oct",
"amount": 480000,
"sourceCurrencyIso3Code": "NGN",
"sourceAccountNumber": "9912345678",
"onBehalfOf": "Kx81mQ2pZr4tV9wYa3Bc5",
"beneficiaryId": "Bn4kQ8rT2vX6yZ1aC3dEf"
}'
- The source account and the beneficiary must both belong to the merchant ("Invalid Source Account Owner" or "Invalid Destination Account Owner" otherwise).
- The fee is charged to the merchant's account, on top of the amount: leave room for it.
- Many merchants at once? Use Bulk payouts: one file per source account.
With auto-sweep, the merchant's wallet doesn't hold the money: settle from your pool account without onBehalfOf, to a beneficiary saved for your own business.
Track each payout as in Payouts.
Data to store
| Field | From | Used for |
|---|---|---|
Customer ID (entityId) |
Create a customer | onBehalfOf on accounts and payouts, subClientId on beneficiaries |
accountId and funding |
Create an account or account_ |
Routing inflows, settlement source, statements |
Settlement beneficiaryId |
Create a beneficiary | Settlement payouts |
Inflow transactionId (IN-…) and Idempotency-Key |
account / inflow |
De-duplication, refunds, reconciliation |
Payout unique (stored before the call) and transactionId |
your own, then the transfer response | Matching transfer webhooks, lookups |
Webhooks to handle
| Event | What to do |
|---|---|
client / subclient_ |
Enable the merchant if status is active; show the reason (approval) if rejected. |
account / account_ |
Save the new funding (it can arrive once per partner bank). |
account / inflow |
Credit the merchant found by receiving. |
transfer / transfer_, transfer_ |
Update the settlement. onBehalfOf names the merchant. |
transfer / refund |
Record the refund outcome, matched by your unique. |
Held or rejected inflows, approver declines and accounts that fail later send no webhook. See Outcomes without a webhook.
Reconciliation
Each morning, for each merchant wallet (or your pool account with auto-sweep), pull yesterday's Get an account statement with a pageSize large enough for the whole day:
- Match credits to the inflows you recorded by
id(= inflowtransactionId). A credit you didn't record is a missed webhook: process it now. - Match debits to settlements by
custom(= yourReference unique), and account forRequest Id Feelines. - Compare
closingwith the balance in your merchant ledger.Balance
List accounts with onBehalfOf lists one merchant's accounts. See Balances and statements.
Variations
- One payment per order: use dynamic accounts. Each accepts one payment of an exact amount, then closes; payments land in your pool account, and a wrong amount sends no webhook.
- Invoices with partial payments: use Receivables.
- Refunds to buyers: Refund an inflow to the original sender (NGN and GHS, one refund per inflow). See Refunds.
Things that trip people up
- All merchants' events come to your one webhook URL. Route inflows by
receiving; with auto-sweep,Account Number accountIdis the pool account's. - "Payment Received" emails for every merchant go to your business contact, not to the merchant.
- Only
activemerchants can have accounts ("Sub-Client not active" otherwise), and a newly approved merchant can take a few minutes to become usable for payouts ("Invalid sub onBehalfOf"). - Give each merchant a distinct business name: two customers with the same name can't both be used for payouts.
- List accounts without
onBehalfOflists your accounts only. With the ID of a merchant that isn'tactive, it falls back to your own accounts. - Refunds check the balance of the account that received the payment. With auto-sweep that account holds nothing, so refunds of swept inflows (including dynamic accounts) are refused; contact Multigate to return such a payment.
Go-live notes
- Confirm with Multigate whether auto-sweep is on in production, and build settlement and refunds for the setting you'll have.
- Merchants' reviews in production depend on Multigate compliance; keep merchants "under review" in your product until
subclient_arrives.approved - Use real email domains for merchants: production refuses disposable ones.
- Work through the go-live checklist.