Skip to content
MultigateAPI
Support Staff

Products

Collections

Collect local-currency payments into named virtual accounts, yours or your customers', and know when money arrives. Payers pay by bank transfer to an account number in the account's fundingDetails.

An inflow is money received into one of your accounts. A virtual account is a bank account number at a partner bank that credits your wallet (your balance on Multigate).

Before you start

  • An active wallet with at least one virtual account: see Accounts.
  • A record, in your own database, of which virtual account number (fundingDetails[].accountNumber) belongs to which of your customers or orders. Inflows are matched on it.
  • A webhook URL configured, so you receive account / inflow: see Webhooks.

How an inflow works

sequenceDiagram
    autonumber
    participant Payer
    participant Bank as Partner bank
    participant MG as Multigate
    participant You as Your backend
    Payer->>Bank: Bank transfer to the virtual account number
    Bank->>MG: Payment notification
    Note over MG: Match to the account, hold the money for screening
    Note over MG: Screening clears: credit the wallet (or your pool account)
    MG-)You: Webhook account / inflow
    MG-->>You: Email "Payment Received" to your business contact
  1. You give a payer one of the account numbers from the account's fundingDetails (Accounts).
  2. The payer sends a bank transfer.
  3. Multigate matches the payment to the account and holds it while compliance screening runs.
  4. When screening clears, Multigate credits the wallet, then sends you account / inflow and a "Payment Received" email. Nothing is sent before the credit.

Inflows into your customers' accounts come to your webhook URL and your business contact email. The payload has no customer ID: identify the account from receivingAccountNumber (or accountNumber in the legacy payload).

Auto-sweep to a pool account

Auto-sweep means each inflow is moved automatically into one pool account (a single wallet that collects for many virtual accounts). If Multigate has set this up for you (always the case for dynamic accounts), inflows are credited to the pool account instead of the receiving wallet. receivingAccountNumber still shows the account the payer paid into, but accountId is the pool account's ID.

Statuses

stateDiagram-v2
    direction LR
    [*] --> screening: payment received
    screening --> credited: screening clears (webhook + email)
    screening --> held: needs review or flagged
    held --> credited: reviewer clears it
    [*] --> rejected: disabled account, or wrong amount on a dynamic account

You only see the credited outcome. Held and rejected inflows send no webhook.

The inflow webhook

{
  "header": { "notification_type": "account", "notification": "inflow", "hash": "6d5c4b3a…" },
  "body": {
    "amount": "150000",
    "paymentReference": "100004261010120455123456789012",
    "transactionId": "IN-261010-1204557781",
    "sessionId": "100004261010120455123456789012",
    "transactionType": "credit",
    "accountName": "Kola Stores Ltd",
    "bankCode": "000017",
    "senderAccountName": "KOLA STORES LTD",
    "senderAccountNumber": "2093847561",
    "currencyIso3Code": "NGN",
    "narration": "Invoice 552 payment",
    "transactionDate": "2026-10-10T12:04:55.000Z",
    "receivingAccountNumber": "9912345678",
    "accountId": "Ac4mW8pZ2kR6tY1nQ7xVb"
  }
}
Field Use it for
transactionId Multigate's ID for the inflow (IN-…). Store it. You need it exactly to refund the inflow, and it is the line id in statements.
amount A decimal string without fixed decimals ("150000", "1500.5"). Parse it as a decimal.
receivingAccountNumber The virtual account the payer paid into. Map it to your merchant or customer.
accountId The wallet credited (the pool account when auto-sweep applies).
accountName, bankCode The receiving virtual account's name and its partner bank's code (not the payer's bank).
senderAccountName, senderAccountNumber, narration Who paid and why. Use the narration to match payments to orders. Any of them can be null: Multigate passes on what the payer's bank sends and leaves a field empty rather than guess it. Some banks send placeholders instead of a value, so treat values like NIL as missing too.
paymentReference, sessionId The bank's reference for the payment (the same value in both fields).
transactionDate When Multigate recorded the payment, not the bank's time.

The payer's bank name isn't sent.

Process it like every webhook: verify the signature, store the Idempotency-Key, and also check you haven't already recorded this transactionId. Multigate retries a delivery your server didn't acknowledge with a 2xx (up to 5 retries, 5 to 10 minutes apart), so the same inflow can arrive more than once.

Test it in the sandbox

Simulate a payment into any active account:

curl -X POST "$MG_BASE_URL/client-service/api/v1/webHook/publishInflow" \
  -H "mp-api-key: $MG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountNumber": "9912345678", "amount": 150000, "currency": "NGN" }'

See Environments and sandbox.

A simulated inflow differs from a real one:

  • senderAccountName is the receiving account's own name, and senderAccountNumber is null.
  • paymentReference and sessionId are a generated ID, which the simulate call doesn't return. Wait for the webhook to learn the transactionId.
  • The narration looks like MULTIGATE-<account name>-<masked account number>.
  • currency is ignored: the account's currency is used.

Make your handler accept null sender fields.

Production ignores simulated inflows

In production, the simulate endpoint still answers 200 "inflow initiated" but does nothing. Keep it out of your production code. See Sandbox vs production.

Inflows held for review

Every inflow is screened before it is credited. Until then the money is held on Multigate's side and isn't in your balance. Screening checks the payer's name against sanctions lists, and the payer's name and narration for other compliance risks. It also notes whether the payer is the account holder itself (for example, a business funding its own wallet from its own bank account) or someone else.

An inflow that needs a closer look waits for Multigate's compliance team. The most common reason is that the payer's bank sent no sender name, so the payer can't be screened automatically. An inflow that screening flags stays held. No webhook or email is sent while an inflow is held, and none is sent if it stays held. If a payer says they've paid and nothing has arrived after a reasonable time, contact support with the amount, date, receiving account and sender details.

Rejected inflows

Some incoming payments are rejected instead of credited:

  • A payment into an account whose business or customer is disabled.
  • A payment of the wrong amount into a dynamic account. Some banks refuse it before it reaches Multigate.

No webhook is sent for a rejected inflow. When Multigate rejects it, your business contact receives a "Rejected Inflow Transaction" email (its text always says the account is disabled). Contact support to return the money to the payer.

Match an inflow to your customer or order

The inflow webhook has no customer ID and no order ID. You match it through the account number the payer paid into, so choose the pattern before you hand out account numbers:

You want to match each payment to Do this Match on
A customer (merchant, business client) Open one account per customer with onBehalfOf (Accounts). Store each of its fundingDetails[].accountNumber against the customer. receivingAccountNumber → your customer
One order or invoice, exact amount Open a dynamic account per order. Store its fundingDetails.accountNumber against the order. receivingAccountNumber → your order
One invoice, with part-payments and overpayments tracked Use Receivables: each invoice gets its own account, and you also receive receivables / inflow with the invoice status. The receivable
Several orders paid into one shared account Ask payers to put your order number in the transfer narration. narration (best effort: payers mistype it, and it can be null)

Then, for each account / inflow webhook:

  1. Verify the signature, and skip it if you've already recorded this transactionId.
  2. Look up receivingAccountNumber (legacy payload: accountNumber) in your records to find the customer or order.
  3. Record transactionId, amount (as a decimal) and currencyIso3Code against it, and credit your customer in your own system.
  4. If no record matches, keep the inflow in an "unmatched" queue for your operations team. Don't drop it.

Don't match on accountId when auto-sweep is set up: it's the pool account, shared by many virtual accounts.

Sending money back

Refund an inflow to its sender with Refunds, using its transactionId.

Reconciliation

Each day, compare the inflows you recorded with the account's statement: Get an account statement lists every credit (line id = the webhook's transactionId) with a running balance. A collection fee, if your agreement has one, is a separate Fee debit line (on a dedicated fee account instead, if Multigate has set one up for you). An inflow in the statement that you didn't record usually means a missed webhook. See Balances and statements.

Next

Open the accounts payers pay into.

One account per merchant, end to end.