Skip to content
MultigateAPI
Support Staff

Webhooks and emails

Event catalogue

Every webhook Multigate sends, when it fires, which API calls cause it, and what it contains. Each event is identified by header.notification_type (the object) and header.notification (what happened).

Trade finance (letters of credit and guarantees) sends no webhooks: poll its lists, and see its emails.

At a glance

notification_type / notification Fires when Caused by
transfer / transfer_completed A transfer is paid. Create a transfer, bulk transfer rows, remittances, approvals in the dashboard
transfer / transfer_failed A transfer fails at the bank or partner, or the balance is short when the payout starts. Same as above
transfer / refund A refund of an inflow completes or fails. Refund an inflow
account / inflow Money is credited to one of your accounts or a customer's. A payer's transfer; Simulate an inflow in the sandbox
account / account_opened An account, or one of its virtual accounts, becomes active. Create an account
client / subclient_approved Multigate finishes reviewing a customer (approved or declined). Submit a customer for review or resubmit a declined customer
receivables / inflow A payment is matched to one of your receivables (invoices you collect against). Sent together with account / inflow. A payer's transfer into a receivable's account
trade / trade_completed A conversion settles. Lock a rate

Events about your customers' activity are sent to your webhook URL. Use onBehalfOf, clientId or the account details in the payload to tell customers apart.

Every payload may gain new fields over time. Ignore fields you don't recognise, and don't rely on the order of fields.

What to do with each event

Event Find your record by What to do
transfer_completed uniqueRequestId: the ID you sent when you created the transfer or remittance. For a bulk row, the ID Multigate generated for it. Mark the payout paid, unless it's already final. Store transactionId for support and reconciliation. A large NGN transfer can be split into parts, each with its own event and your ID plus -1, -2, …; the payout is fully paid when every part is completed.
transfer_failed uniqueRequestId Mark the payout failed and show responseMessage to your user. Any amount and fee debited are back in the source account (with responseCode 22, the balance was too low and nothing was debited; its responseMessage is the raw key insufficientBalance today, so match on the code). To try again, create a new transfer with a new uniqueRequestId.
refund uniqueRequestId: the ID you sent with the refund. completed: the payer has the money back. failed: no money moved, and you can try again.
account / inflow transactionId: Multigate's unique ID for the payment. Record the credit once per transactionId. Work out whose money it is from receivingAccountNumber (match it to the account numbers you saved from account_opened). Parse amount as a decimal. Keep transactionId: you need it to refund the payment.
account_opened accountId (returned by Create an account) or your uniqueRequestId. Save the account numbers and bank details in fundingDetails, and show them to the people who will pay in. Add new ones; don't delete numbers missing from a later event, because events can arrive out of order.
subclient_approved entityId: the customer ID returned when you created the customer. status active: the customer is approved, and you can open accounts and pay out for it with onBehalfOf. rejected: show approvalComments if present, fix the problem, then resubmit.
receivables / inflow receivableDetails.entityId (the receivable ID) and transactionDetails.transactionId (the payment). Record the payment once per transactionId, and update the invoice to receivableDetails.status.
trade_completed id: the quote ID you locked. Act only on status completed, and check the target account was credited before you release money (see the warning below).

Transfer events

sequenceDiagram
    autonumber
    participant You as Your backend
    participant MG as Multigate
    participant Bank as Bank or partner
    You->>MG: Create a transfer
    MG-->>You: 200, status processing
    MG->>Bank: Payment
    alt Paid
        Bank-->>MG: Success
        MG-)You: transfer / transfer_completed
    else Failed
        Bank-->>MG: Failure reason
        MG-)You: transfer / transfer_failed
    end
{
  "header": { "notification_type": "transfer", "notification": "transfer_completed", "hash": "3f6c0a9e…" },
  "body": {
    "transactionId": "PMT-261010-093000482913",
    "uniqueRequestId": "po_20261010_0001",
    "status": "completed",
    "responseCode": "00",
    "responseMessage": "successful",
    "onBehalfOf": "Ab3kP9xQ2mN7vR1sT4uV6",
    "amount": 250000,
    "charge": 50,
    "totalAmount": 250050,
    "chargesType": "",
    "transferType": "NIP",
    "sourceAccountNumber": "1029384756",
    "sourceCurrencyIso3Code": "NGN",
    "sourceCountryIso3Code": "NGA",
    "sourceBankBicOrCode": "multigate",
    "beneficiaryDetails": {
      "accountNumber": "0123456789",
      "accountName": "Ada Okafor",
      "bankName": "Guaranty Trust Bank",
      "bankCode": "058",
      "intermediaryBank": { "bankName": "", "bankCode": "", "swiftCode": "" }
    },
    "approvedBy": "",
    "createdAt": "2026-10-10T09:30:00.482Z",
    "updatedAt": "2026-10-10T09:30:09.734Z"
  }
}

Same fields, with status: "failed". responseMessage and responseCode carry the reason, often the bank's own wording.

{
  "header": { "notification_type": "transfer", "notification": "transfer_failed", "hash": "8a1b2c3d…" },
  "body": {
    "transactionId": "PMT-261010-101544730021",
    "uniqueRequestId": "po_20261010_0005",
    "status": "failed",
    "responseCode": "25",
    "responseMessage": "Beneficiary account is dormant",
    "amount": 75000,
    "charge": 50,
    "totalAmount": 75050
  }
}

transactionId is the ID of the original inflow you refunded.

{
  "header": { "notification_type": "transfer", "notification": "refund", "hash": "0f1e2d3c…" },
  "body": {
    "transactionId": "IN-261010-0915304412",
    "uniqueRequestId": "rf_20261010_0001",
    "currencyIso3Code": "NGN",
    "amount": 5000,
    "status": "completed",
    "responseMessage": "successful",
    "responseCode": "00"
  }
}
Field Notes
transactionId Multigate's transfer ID (PMT-…). For a refund, the ID of the original inflow.
uniqueRequestId Your idempotency key: the unique ID you sent with the request. For parts of a split NGN transfer: yours with -1, -2, …, one event per part.
status completed or failed.
responseCode, responseMessage 00 / successful when paid. On failure, a code and either a short machine key (such as failed) or the bank's own text. Show the text to people; don't parse it.
amount, charge, totalAmount Numbers in major units. totalAmount = amount + charge.
approvedBy Email of the user who approved the transfer, or empty.

Account events

{
  "header": { "notification_type": "account", "notification": "inflow", "hash": "6d5c4b3a…" },
  "body": {
    "amount": "150000",
    "transactionId": "IN-261010-1204557781",
    "transactionType": "credit",
    "currencyIso3Code": "NGN",
    "receivingAccountNumber": "9912345678",
    "accountId": "Ac4mW8pZ2kR6tY1nQ7xVb",
    "accountName": "Kola Stores Ltd",
    "senderAccountName": "KOLA STORES LTD",
    "senderAccountNumber": "2093847561",
    "narration": "Invoice 552 payment",
    "sessionId": "100004261010120455123456789012",
    "paymentReference": "100004261010120455123456789012",
    "bankCode": "000017",
    "transactionDate": "2026-10-10T12:04:55.000Z"
  }
}

amount is a decimal string with no fixed number of decimal places ("150000", "1500.5"): parse it as a decimal, never as a float. Unknown values are null or left out. Some businesses receive the legacy version, which has accountNumber instead of receivingAccountNumber and accountId.

The event is sent only after the inflow clears compliance screening. senderBankName isn't sent today, and paymentReference and sessionId carry the same bank reference. If Multigate has set up auto-sweep to a pool account for you (inflows are then credited to one central account; always the case for dynamic accounts), accountId is the pool account's ID; match payments on receivingAccountNumber.

A simulated inflow in the sandbox has senderAccountName set to the receiving account's own name, senderAccountNumber null, and a generated paymentReference/sessionId that the simulate call doesn't return. Each payment into a receivable's account also sends this event.

De-duplicate inflows on transactionId: the same inflow can arrive as a second delivery with a new Idempotency-Key. If your business has agreed with Multigate to skip webhooks for GHS inflows, those inflows send no event; you still get the "Payment Received" email.

{
  "header": { "notification_type": "account", "notification": "account_opened", "hash": "9e8d7c6b…" },
  "body": {
    "accountId": "Ac4mW8pZ2kR6tY1nQ7xVb",
    "clientId": "Kx81mQ2pZr4tV9wYa3Bc5",
    "entityId": "Kx81mQ2pZr4tV9wYa3Bc5",
    "uniqueRequestId": "acct_20261010_0001",
    "currencyIso3Code": "NGN",
    "accountName": "Acme Payments - Kola Stores",
    "status": "active",
    "availableBalance": 0,
    "accountType": "wallet",
    "fundingDetails": [
      {
        "accountName": "Acme Payments - Kola Stores",
        "bankOrSortCode": "000017",
        "accountNumber": "9912345678",
        "bankCountry": "NGA",
        "bankName": "Partner Bank",
        "bankAddress": null,
        "countryIso3Code": "NGA",
        "bic": null
      }
    ]
  }
}

fundingDetails lists the account numbers payers can send to. You may receive this event more than once for one account, as each partner bank activates. For a customer's account, clientId and entityId are the customer's ID. availableBalance is always 0 here; read balances through the API.

Customer events

client / subclient_approved is sent when Multigate's compliance review of a customer finishes. Check status: active means approved, rejected means declined. Ignore approvalStatus: it doesn't reflect Multigate's decision.

The body is the customer record (the same fields as Get a customer), shortened here:

{
  "header": { "notification_type": "client", "notification": "subclient_approved", "hash": "7d1e9a0c…" },
  "body": {
    "name": "Kola Stores Ltd",
    "subClientType": "client",
    "businessName": "Kola Stores Ltd",
    "approvalStatus": "approved",
    "entityId": "Kx81mQ2pZr4tV9wYa3Bc5",
    "status": "rejected",
    "createdAt": "2026-10-09T08:00:00.000Z",
    "updatedAt": "2026-10-10T14:20:00.000Z",
    "approvalComments": "Ownership documents could not be verified.",
    "approvalDate": "Sat Oct 10 2026 14:20:00 GMT+0000 (Coordinated Universal Time)",
    "parentClientId": "Ab3kP9xQ2mN7vR1sT4uV6"
  }
}
  • approvalComments and approvalDate appear only when a reviewer decided manually. A customer approved without manual review has neither.
  • approvalDate is free text, not ISO 8601. Use the time you received the event if you need one.
  • Subsidiaries are approved straight away and send no event.

Receivable and conversion events

{
  "header": { "notification_type": "receivables", "notification": "inflow", "hash": "5a6b7c8d…" },
  "body": {
    "receivableDetails": {
      "entityId": "Rc4mW8pZ2kR6tY1nQ7xVb",
      "invoiceRef": "INV-2026-0042",
      "amount": 500000,
      "amountPaid": 500000,
      "currencyIso3Code": "NGN",
      "dueDate": "2026-10-31T00:00:00.000Z",
      "paidDate": "2026-10-10T12:04:55.000Z",
      "status": "paid"
    },
    "transactionDetails": {
      "amount": 500000,
      "transactionId": "IN-261010-1204557790",
      "senderAccountName": "KOLA STORES LTD",
      "transactionDate": "2026-10-10T12:04:55.000Z"
    }
  }
}

status is partially_paid, paid or overpaid, and amountPaid is the running total. Amounts are numbers. The same payment also sends account / inflow.

{
  "header": { "notification_type": "trade", "notification": "trade_completed", "hash": "c4d5e6f7…" },
  "body": {
    "id": "FX-261010-150000482913",
    "sourceAmount": "1500000",
    "sourceCurrency": "NGN",
    "rate": "0.000654",
    "targetCurrency": "USD",
    "targetAmount": "981",
    "tradeReference": "FXT-261010-0007",
    "status": "completed"
  }
}

The event can arrive before the Lock a rate response when the conversion completes straight away: match it by id (the quote ID). Amounts and the rate are decimal strings with no fixed number of decimal places. status is completed when the event is sent at settlement; an event Multigate re-sends later carries the conversion's current status, so act only on completed.

Confirm conversions before releasing funds

Today trade_completed is also sent when the liquidity provider reports a failed conversion. The source debit is reversed, but the event still says completed, and so does Get a conversion. Before you release money based on a conversion, check that the target account was actually credited (its statement or balance).

Full field lists are in the API reference under Webhooks › Events.

Outcomes without a webhook

These outcomes don't send a webhook today. Detect them by checking status through the API:

Outcome How to detect it
A transfer declined by an approver in your business or in Multigate's compliance or settlement review (rejected), or stopped by compliance screening (failed, responseCode 43) Find transfers
A USD payout waiting for Multigate's operations team Stays processing; no event until it completes or fails
A refund that fails because the receiving account's balance couldn't be reserved just before payment Find transfers ?uniqueRequestId=; status is failed
A transfer waiting for approval (pending_approval) The create response, or Find transfers
Some transfer failures: a compliance stop, a Nigerian account number that isn't 10 digits, a debit that couldn't be posted Find transfers; status is failed
A transfer that needs manual investigation stays processing Contact support if it's still processing after 24 hours
Any event that failed all its retries, or happened while no webhook URL was registered Not sent again; fetch the object through the API
An inflow that was rejected (a disabled business, or the wrong amount into a dynamic account) Your business contact receives a "Rejected Inflow Transaction" email. Some banks refuse the payment before it reaches Multigate
An account that fails to open after the request Nothing; poll Get an account by ID
An inflow held for compliance review Not credited and no webhook until cleared; contact support if an expected payment is missing
A conversion that failed when you locked it The Lock a rate call returns an error
A customer created or submitted No event until compliance finishes reviewing
A receivable becoming unpaid (ready) or failed Poll Get a receivable. If the bank opens the account after a delay, an account / account_opened event with accountType receivable is sent
A subsidiary customer approved straight away No event; Get a customer shows active
An account activated later by Multigate's approval of the bank profile behind it Often no account_opened; poll accounts that stay processing
A beneficiary saved, a bulk batch committed No event; each bulk row sends its own transfer event
A bank account linking request submitted, approved or declined Approvers get an email on submission; check the request with Search linking requests
A linked account created (on approval) No account_opened event; list your accounts to find it
A payout from a linked NGN account waiting for funds or for bank credentials Stays processing; check your bank balance and that credentials are uploaded

Events that aren't sent

  • account / reversal is defined on the platform but isn't sent today. Use the transfer_failed event, or the transfer's failed status, as your signal that a payout didn't happen.