Skip to content
MultigateAPI
Support Staff

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_KEY and MG_CLIENT_ID set 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

Onboard each merchant

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

Open an account per merchant

Once the merchant is active, call Create an account with onBehalfOf set to the customer ID and a new uniqueRequestId:

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.accountId and every fundingDetails[].accountNumber against the merchant, and show the merchant their account number. Payers see the merchant's registered business name (fundingDetails[].accountName).

If status is processing, fundingDetails is empty: wait for account / account_opened (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.

Credit orders from the inflow webhook

Every payment sends account / inflow to your webhook URL. The payload has no customer ID, so:

  1. Verify the signature, then store the Idempotency-Key header.
  2. Skip it if you've already recorded its transactionId (the same inflow can be delivered twice).
  3. Find the merchant by receivingAccountNumber (the number the buyer paid into), never by accountId.
  4. Parse amount (a decimal string) as a decimal, and match the order by narration or your own order logic.
  5. Store transactionId (IN-…): you need it for refunds and reconciliation.

Details: Collections › The inflow webhook. In the sandbox, test with Simulate an inflow.

Save each merchant's settlement bank account

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.entityId as the merchant's settlement beneficiaryId. See Beneficiaries for your customers.

Settle merchants

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 fundingDetails[].accountNumber Create an account or account_opened 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 uniqueRequestId (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_approved Enable the merchant if status is active; show the reason (approvalComments) if rejected.
account / account_opened Save the new fundingDetails (it can arrive once per partner bank).
account / inflow Credit the merchant found by receivingAccountNumber.
transfer / transfer_completed, transfer_failed Update the settlement. onBehalfOf names the merchant.
transfer / refund Record the refund outcome, matched by your uniqueRequestId.

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 (= inflow transactionId). A credit you didn't record is a missed webhook: process it now.
  • Match debits to settlements by customReference (= your uniqueRequestId), and account for Fee lines.
  • Compare closingBalance with the balance in your merchant ledger.

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 receivingAccountNumber; with auto-sweep, accountId is the pool account's.
  • "Payment Received" emails for every merchant go to your business contact, not to the merchant.
  • Only active merchants 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 onBehalfOf lists your accounts only. With the ID of a merchant that isn't active, 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_approved arrives.
  • Use real email domains for merchants: production refuses disposable ones.
  • Work through the go-live checklist.