Skip to content
MultigateAPI
Support Staff

Use cases

Cross-border payouts

Pay suppliers, contractors or partners abroad from your local-currency balance: convert NGN to USD at a locked rate, then pay out by international bank transfer (SWIFT, the network banks use to send money abroad).

Who it's for: corporate enterprises and SMEs paying suppliers abroad, and fintechs offering international payouts.

Architecture

flowchart LR
    you[Your backend] -->|1 quote and lock| fx[FX conversion]
    ngn[(Your NGN account)] -->|debited sourceAmount| fx
    fx -->|credited targetAmount| usd[(Your USD account)]
    you -->|2 create transfer| usd
    usd -->|SWIFT payment| bank([Supplier's bank abroad])
    fx -.->|trade_completed| hook[Your webhook endpoint]
    usd -.->|transfer_completed or transfer_failed| hook
sequenceDiagram
    autonumber
    participant You as Your backend
    participant MG as Multigate
    participant Bank as Supplier's bank abroad
    You->>MG: Get a transfer fee (USD account, supplier amount)
    You->>MG: Get a quote NGN to USD (amount = supplier amount + fee)
    MG-->>You: Quote id, buyRate, endTime (about 60 seconds)
    You->>MG: Lock the rate (quote id, NGN accountId, USD accountId)
    MG-->>You: status completed (or processing)
    MG-)You: trade / trade_completed
    You->>MG: Create a transfer from the USD account (SWIFT, chargeType, assets)
    MG-->>You: 200, status pending (screening)
    Note over MG: Screening, approval, debit, then payment
    MG->>Bank: SWIFT payment
    MG-)You: transfer / transfer_completed or transfer_failed

Before you start

  • A sandbox API key, and the variables MG_BASE_URL, MG_API_KEY and MG_CLIENT_ID set as in the Quickstart.
  • A webhook URL registered (Webhooks overview). Events that happen before you register it are never delivered.
  • Your API user has the lock rates permission (to convert) and the initiate payment permission (to quote fees and pay). See Approvals for whether its payments also need an approver.
  • Multigate has set up partner banks for NGN and USD for your business, and you have accepted their terms of service in the dashboard.
  • Supporting documents. A USD payout to an account whose name doesn't match your own account name is a third-party payment and needs supporting documents, such as the supplier's invoice. Paying a supplier is almost always third-party. The upload endpoint isn't public yet: ask Multigate how to upload documents and get their IDs.
Known issue: no conversions for customers

Today a quote or lock with onBehalfOf is refused with 400 "Invalid onBehalfOf id". If you pay out for your customers (sub-clients), convert in your own business's accounts until Multigate fixes this. See FX conversions.

Build plan

Open an NGN and a USD account

Open one account in each currency with Create an account, each with its own uniqueRequestId (your unique ID for the request, so a retry isn't executed twice: see Idempotency). Follow Accounts › Open an account.

From each response, store:

  • account.accountId: the conversion needs it as sourceAccountId (NGN) and targetAccountId (USD).
  • An account number for payouts: the wallet's own number or one of its fundingDetails[].accountNumber values.

If an account comes back processing, wait for the account / account_opened webhook. Some USD wallets have no virtual account (fundingDetails is empty); that's expected, because you fund them by conversion.

Fund the NGN account by bank transfer to its fundingDetails account number. In the sandbox, use Simulate an inflow.

Save the supplier as a beneficiary

Save the supplier once with Create a beneficiary, and keep beneficiary.entityId as their beneficiaryId. For USD, bankBicOrCode is the bank's SWIFT BIC (an 8- or 11-character bank code such as COBADEFFXXX). Send the address, city, state and a real postCode (it defaults to 000000). See Beneficiaries › Rules by currency.

You can skip this step and send beneficiaryDetails on the transfer instead: Multigate then saves the beneficiary for you.

Quote the payout fee

Multigate's fee is charged to the USD account on top of the amount, so the USD account must hold both. Quote it with Get a transfer fee:

curl "$MG_BASE_URL/payment-service/api/v1/transfer/get-transfer-fee?amount=18000&sourceCurrencyIso3Code=USD&sourceAccountNumber=4410023391&beneficiaryId=Bn4kQ8rT2vX6yZ1aC3dEf" \
  -H "mp-api-key: $MG_API_KEY"
{ "message": "Successful", "feeAmount": 25, "feeCurrency": "USD", "isFeePercentage": false, "transactionCharge": 25, "beneficiaryWillReceive": 18000 }

The fee shown is an example; yours comes from your pricing. You need 18000 + feeAmount USD: here 18,025.

Convert NGN to USD

Get a quote with Get a quote, where amount is what you want to receive in USD, then lock it with Lock a rate before endTime (about 60 seconds). Both calls need the entityId header set to your client ID.

curl "$MG_BASE_URL/trade-service/api/v1/fx-rates/best-rate?sourceCurrency=NGN&targetCurrency=USD&amount=18025" \
  -H "mp-api-key: $MG_API_KEY" \
  -H "entityId: $MG_CLIENT_ID"

Show your user buyRate (NGN per USD), store the quote id, then lock:

curl -X POST "$MG_BASE_URL/trade-service/api/v1/fx-rates/lock-rate" \
  -H "mp-api-key: $MG_API_KEY" \
  -H "entityId: $MG_CLIENT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "FX-261010-150000482913",
    "sourceAccountId": "Ac4mW8pZ2kR6tY1nQ7xVb",
    "targetAccountId": "Ac7tY2nQ5xVb9mW3pZ8kR",
    "amount": 18025,
    "reference": "acme_conv_20261010_0007"
  }'
  • status completed: the USD account is credited. Go on.
  • status processing: wait for trade / trade_completed, or poll Get a conversion with the quote ID.
  • Before paying out after a conversion that was processing, check that the USD account actually received targetAmount (Get an account balance): today trade_completed is also sent when the provider fails a conversion later.

Full rules, retries and errors: FX conversions.

Create the SWIFT payout

Generate a uniqueRequestId, store it, then call Create a transfer from the USD account:

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_po_20261010_0003",
    "reference": "Invoice LT-2026-118",
    "amount": 18000,
    "sourceCurrencyIso3Code": "USD",
    "sourceAccountNumber": "4410023391",
    "beneficiaryId": "Bn4kQ8rT2vX6yZ1aC3dEf",
    "paymentMethod": "SWIFT",
    "chargeType": "SHA",
    "assets": ["3c9d7e21-5b4a-4f6e-8a1d-2e7f9c0b6a51"]
  }'
  • assets: the IDs of the supporting documents (see Before you start). Without them a third-party USD payout is refused with 400 "Supporting documents are required for 3rd party USD transfers."
  • chargeType: who pays the banks' SWIFT charges (table below).
  • With beneficiaryDetails instead of beneficiaryId, add intermediaryBank (bankName, bic, countryIso3Code) when the supplier's bank needs a correspondent bank.
  • Store transactionId (PMT-…) with your uniqueRequestId. The response status is pending (being screened), pending_approval (waiting for your approver) or processing.

If the call times out or returns 5xx, the outcome is unknown: follow Payouts › After you send (step "If the create call itself gets no answer") and never retry with a new uniqueRequestId.

Confirm and send the receipt

Wait for transfer / transfer_completed or transfer_failed, matched by uniqueRequestId. USD payouts are completed by Multigate's operations team, so they stay processing longer than local transfers: show "processing" in your UI. If one isn't final after 24 hours, contact support with its transactionId.

When it's completed, give the supplier the PDF from Download a transfer receipt.

Choose who pays SWIFT charges

chargeType Effect
OUR You pay all charges; the supplier receives the full amount.
SHA Charges are shared; intermediary banks may deduct some. The default when you leave chargeType out.
BEN The supplier pays the charges.

chargeType isn't validated, so send one of these values exactly. Multigate's own fee is separate and always charged to your account on top of the amount.

Data to store

Field From Used for
NGN and USD accountId Create an account sourceAccountId and targetAccountId on the lock
USD account number the account's number or fundingDetails sourceAccountNumber on the fee quote and the transfer
beneficiaryId Create a beneficiary (beneficiary.entityId) Paying the same supplier again
Quote id and your reference Get a quote, your own Retrying a lock, matching trade_completed
sourceAmount, targetAmount, rate the lock response Reconciling the conversion
uniqueRequestId (stored before the call) and transactionId your own, then the transfer response Matching webhooks, lookups, support

Webhooks to handle

Event What to do
trade / trade_completed Match by id (the quote ID). It can arrive before the lock response. Check the USD balance before paying out after a processing conversion.
transfer / transfer_completed Mark the payout paid; send the receipt.
transfer / transfer_failed Mark it failed and show responseMessage. A new attempt needs a new uniqueRequestId.

Some outcomes send no webhook: a decline by your approver (rejected), a compliance decline (failed, responseCode 43) and a debit that couldn't be posted (failed, 03). Poll Find transfers ?uniqueRequestId= for payouts that stay non-final. See Outcomes without a webhook.

Reconciliation

Each day, pull Get an account statement for both accounts (Balances and statements):

  • NGN account: a Trade debit of sourceAmount per conversion.
  • USD account: a Trade credit of targetAmount per conversion, and a Transfer debit per payout with customReference = your uniqueRequestId, plus its fee.

Anything only in your records is usually a payout still in a non-final status; anything only in the statement is usually a webhook you didn't process.

Things that trip people up

  • The transfer currency must match the source account: you can't pay USD from an NGN account. Convert first.
  • The balance isn't reserved when you create the transfer; it's checked just before the debit. Keep the USD in the account until the payout is final: if it's short at that check, the payout ends failed (22, with a webhook), and if it drops right after the check, failed (03) without one.
  • In the sandbox an unfunded lock succeeds; production refuses it with 402 "Insufficient balance". Check the NGN balance before you lock. See Sandbox vs production.
  • Never lock a second quote for the same payout until you've confirmed the first didn't go through: a quote ID is the only retry key for a conversion.

Go-live notes

  • Agree USD payout pricing and the supporting-document process with Multigate.
  • Decide with your admin who approves payouts (maker-checker: one person creates, another approves), and test the pending_approval path.
  • Re-test an FX lock larger than your balance in production on day one and handle the 402.
  • Work through the go-live checklist.