# FAQ and troubleshooting

Answers to the problems partners hit most, grouped by symptom. Each answer ends with the page that has the fix.

## Errors on any request

<AccordionGroup>
<Accordion title="A request returns 401">
With an API key: the `mp-api-key` header is missing or empty, the key is mistyped or cut short, it belongs to the other environment (production keys start with `mgk_live_`, sandbox keys with `mgk_sandbox_`), or it was revoked. The request also fails if it sends both `mp-api-key` and an `Authorization` header, or sends the key as `Authorization: Bearer mgk_…` instead of in `mp-api-key`. Every service except the user service also answers `401` when your business has an IP allow-list and the request comes from an address that isn't on it: if a key that works from one server gets 401 from another, check that server's address is on your allow-list. Retrying with the same key doesn't help: fix the key, then resend the same request. See [When a request returns 401](../01-get-started/06-authentication.md#when-a-request-returns-401).
</Accordion>
<Accordion title="A payment or FX request returns 403 'Forbidden' or 'Unauthorized access'">
Your API user lacks the permission for that action. Each endpoint's description in the API reference names the permission it needs. Check what your API user has with [Get the current user](api:getCurrentUser) (`permissions[].permissionId`), then ask your dashboard administrator or Multigate contact to add the missing one. On FX endpoints, `403` also means the `entityId` header isn't one of your businesses: set it to your client ID. See [Errors](../02-concepts/02-errors.md#what-to-do-for-each-status).
</Accordion>
<Accordion title="A customer, account, beneficiary or receivable request returns 404 'Resource not found'">
On the client service, a missing permission answers `404` "Resource not found", not `403`. Check your API user's permissions with [Get the current user](api:getCurrentUser) and ask your administrator to add the one the endpoint names (for example *view accounts*). See [Errors](../02-concepts/02-errors.md#what-to-do-for-each-status).
</Accordion>
<Accordion title="A request returns 404 for an ID I know exists">
Check three things: the ID comes from the **same environment** (sandbox IDs don't exist in production), it's the right kind of ID (an `accountId` isn't an account number; a customer ID isn't your client ID), and it belongs to your business or one of your customers. See [Acting for your customers](../02-concepts/06-acting-for-customers.md#your-client-id-and-your-customers-ids).
</Accordion>
<Accordion title="List accounts or List beneficiaries returns 404">
These two lists answer `404` ("account not found", "beneficiary not found") when nothing matches your filters, or when you ask for a page past the last one. Treat it as an empty list. See [Pagination](../02-concepts/04-pagination.md#response).
</Accordion>
<Accordion title="A request returns 409">
Something already exists: a refund of this inflow ("Refund currently processing", "Refund completed already"), a lock of this quote ("Rate already locked"), an FX `reference` ("Duplicate reference"), a beneficiary ("beneficiaries already exist"), a customer email ("Sub-Client already exists"), an account in that foreign currency at every partner bank ("account already exist") or an open approval request. Don't send it again; look up the existing object. See [Idempotency](../02-concepts/03-idempotency.md#per-operation).
</Accordion>
<Accordion title="A 400 says 'failed_validation'">
That's fixed text. The reasons are in the `errors` array: each entry names the field (`fieldName`) and the problem. See [Error body shapes](../02-concepts/02-errors.md#error-body-shapes).
</Accordion>
<Accordion title="A field I sent was ignored, with no error">
Fields an endpoint doesn't define are removed, not rejected, so a misspelled optional field is silently dropped. Compare your payload with the API reference. See [Errors](../02-concepts/02-errors.md#error-body-shapes).
</Accordion>
<Accordion title="I got an HTML page, a 502 or a 504">
The gateway couldn't reach the service or stopped waiting after 60 seconds. For a request that moves money, the outcome is **unknown**: look it up before you retry, and retry only with the same `uniqueRequestId`. See [Unknown outcomes](../02-concepts/02-errors.md#unknown-outcomes).
</Accordion>
</AccordionGroup>

## Authentication

<AccordionGroup>
<Accordion title="A request returns 403 'This action is not allowed with an API key'">
You tried to manage API keys or credentials as an API user: keys are managed by dashboard users only. To stop a key working, revoke it in the dashboard (**Developer Tool › API Configuration › API keys**). See [Revoke an API key](../01-get-started/06-authentication.md#revoke-an-api-key).
</Accordion>
<Accordion title="I can't generate another API key">
Your business already has 2 active keys, the most allowed. Revoke the one you no longer use, then generate the new one. See [Rotate an API key](../01-get-started/06-authentication.md#rotate-an-api-key).
</Accordion>
<Accordion title="A request returns 403 'Unauthorized access'">
Your business has an IP allow-list and the request came from another address. This `403` comes from the user service. With an API key, every other service refuses an unlisted address with `401` instead. Add your servers' public outbound IP addresses to the list, or ask Multigate to. The allow-list is enforced in the sandbox too, with its own list. See [IP allow-list](../01-get-started/06-authentication.md#ip-allow-list).
</Accordion>
</AccordionGroup>

## Payouts

<AccordionGroup>
<Accordion title="I got a timeout when creating a transfer. Was it created?">
Maybe. Look it up with [Find transfers](api:listTransfers) `?uniqueRequestId=<yours>,<yours>-1` before doing anything else. Not found? Wait at least a minute, look again, then retry with the **same** `uniqueRequestId`. Never resend with a new one. See [Idempotency](../02-concepts/03-idempotency.md#retrying-safely).
</Accordion>
<Accordion title="'Unique request ID already exist'">
That `uniqueRequestId` was used before, possibly by a request you thought had failed. Look up the existing transfer. If you find nothing, another Multigate business used the same ID: send your payout with a new, prefixed ID. See [Idempotency](../02-concepts/03-idempotency.md).
</Accordion>
<Accordion title="My transfer is stuck in pending_approval">
Your API user can't approve its own payments. An approver must approve it in the dashboard, or ask Multigate to give your API user the approve permission too. See [Approvals](../04-products/05-payouts.md#approvals).
</Accordion>
<Accordion title="My transfer has been processing for a long time">
Bank payments can take time, especially SWIFT. In production, Multigate checks pending payouts with the bank for 24 hours. USD payouts are settled by Multigate's operations team, so they stay `processing` until the team completes them. For a linked account, check your bank balance and that credentials are uploaded. Still `processing` after 24 hours? Contact support with the `transactionId`. See [Transfer statuses](../04-products/05-payouts.md#transfer-statuses).
</Accordion>
<Accordion title="A transfer failed or was rejected, but no webhook came">
Some failures and all rejections send no webhook. Poll any transfer that stays non-final in your system with [Find transfers](api:listTransfers). See [Outcomes without a webhook](../03-webhooks-and-emails/02-event-catalogue.md#outcomes-without-a-webhook).
</Accordion>
<Accordion title="My NGN transfer was split into several">
Amounts above the instant-transfer limit are sent as several payments. Each has your `uniqueRequestId` plus a suffix (`-1`, `-2`), its own `transactionId` and its own webhook. Look the parts up with `?uniqueRequestId=<id>-1,<id>-2`. See [Large NGN transfers are split](../04-products/05-payouts.md#large-ngn-transfers-are-split).
</Accordion>
</AccordionGroup>

## Webhooks

<AccordionGroup>
<Accordion title="No webhooks arrive">
Check that your webhook URL is registered **in this environment** with [Get client configurations](api:getClientConfigurations), uses HTTPS with a valid certificate, doesn't redirect, and returns `2xx` within 9 seconds. Events that happened before the URL was registered are never delivered, and an event that fails all 5 retries isn't sent again. Some outcomes never send a webhook at all. If you develop locally, check that your tunnel is running and that its current address is the registered one. See [Webhooks overview](../03-webhooks-and-emails/01-overview.md#how-delivery-works) and [Outcomes without a webhook](../03-webhooks-and-emails/02-event-catalogue.md#outcomes-without-a-webhook).
</Accordion>
<Accordion title="Signatures never match">
Sign only the `body` part of the request, with the client **secret** (not the client key), using the raw bytes as received. If they stopped matching on a certain date, the client secret was rotated: update it in your webhook handler. Also check that no JSON body parser (for example `app.use(express.json())`) runs before your webhook route. See [Verifying signatures](../03-webhooks-and-emails/03-verifying-signatures.md#common-mistakes) and [Build your receiver](../03-webhooks-and-emails/01-overview.md#build-your-receiver).
</Accordion>
<Accordion title="The hash or notification_type header is missing">
Your proxy drops headers with underscores. Read `header.hash` and `header.notification_type` from the JSON body. See [Verifying signatures](../03-webhooks-and-emails/03-verifying-signatures.md#common-mistakes).
</Accordion>
<Accordion title="I received the same event twice">
Expected. De-duplicate by the `Idempotency-Key` header and by the object's ID and status. See [Handle events safely](../03-webhooks-and-emails/01-overview.md#handle-events-safely).
</Accordion>
</AccordionGroup>

## Sandbox vs production

<AccordionGroup>
<Accordion title="It worked in the sandbox but fails in production">
A few checks are off in the sandbox. The usual causes: an FX lock without enough balance (`402` in production), an NGN beneficiary with a wrong account number (`400` "Invalid beneficiary account number"), or a disposable email domain such as yopmail.com ("Email domain is not allowed"). Also check that your production servers' IP addresses are on your production allow-list. See [Sandbox vs production](../01-get-started/05-sandbox-vs-production.md#at-a-glance).
</Accordion>
<Accordion title="My sandbox payout has been processing for 15 minutes">
Expected. The sandbox checks pending payouts with the bank every 15 minutes, and test banks can take longer or never finish. Production is much faster. See [Payout results and timing](../01-get-started/05-sandbox-vs-production.md#payout-results-and-timing).
</Accordion>
<Accordion title="The same sandbox payout completed once and failed the next time">
Some simulated partners in the sandbox choose the outcome at random. Test that you handle each outcome, not that you get a particular one. See [Payout results and timing](../01-get-started/05-sandbox-vs-production.md#payout-results-and-timing).
</Accordion>
<Accordion title="My sandbox customer has been processing for days">
Compliance results are collected far less often in the sandbox. Ask your Multigate contact to complete the review for your test customer. See [Customers and compliance review](../01-get-started/05-sandbox-vs-production.md#customers-and-compliance-review).
</Accordion>
<Accordion title="Name enquiry in the sandbox returns a name that isn't the one I expected">
Sandbox account checks are answered by test banks, which can return fixed test names. Production returns the real account holder's name. See [Beneficiaries and name enquiry](../01-get-started/05-sandbox-vs-production.md#beneficiaries-and-name-enquiry).
</Accordion>
<Accordion title="Simulate an inflow returns 200 in production but no money arrives">
Expected: the endpoint only works in the sandbox. In production, money arrives only from real bank transfers to your virtual accounts. See [Inflows and virtual accounts](../01-get-started/05-sandbox-vs-production.md#inflows-and-virtual-accounts).
</Accordion>
</AccordionGroup>

## Customers

<AccordionGroup>
<Accordion title="'Invalid sub onBehalfOf' right after a customer was approved">
A customer becomes usable for payouts a few minutes after it turns `active`. Also check that none of your other customers has the same business name: a duplicate name stops the second customer from being usable. See [Acting for your customers](../02-concepts/06-acting-for-customers.md#only-active-customers).
</Accordion>
<Accordion title="I can't open an account for a customer">
The customer must be `active`. A customer still in `draft` or `processing` gets "Sub-Client not active". See [Customer statuses](../04-products/01-customers.md#customer-statuses).
</Accordion>
</AccordionGroup>

## Accounts and collections

<AccordionGroup>
<Accordion title="My new account has no account number">
The partner bank hasn't confirmed it yet (`status: processing`). Wait for `account_opened`, and poll [Get an account by ID](api:getAccountById) in case the webhook doesn't come. See [When the account isn't active yet](../04-products/02-accounts.md#when-the-account-isnt-active-yet).
</Accordion>
<Accordion title="Simulate an inflow returned 200 but nothing happened">
The account isn't active yet: the body says "account has not been approved". Wait for `account_opened`, then retry. See [Simulate an inflow](../01-get-started/04-environments-and-sandbox.md#simulate-an-inflow).
</Accordion>
<Accordion title="A linked account's balance shows 0">
The bank couldn't be reached, or credentials aren't uploaded. Retry, and check the credentials list with [List bank credential slots](api:listBankCredentials) (`isProfiled`). See [Using a linked account](../04-products/02-accounts.md#using-a-linked-account).
</Accordion>
</AccordionGroup>

## Trade finance

<AccordionGroup>
<Accordion title="Every trade finance call returns 403 forbidden">
Your API user lacks the trade finance permission the endpoint needs, or your business doesn't have the Trade Finance module. Check `permissions` with [Get the current user](api:getCurrentUser) and see [Who can do what](../04-products/12-trade-finance.md#who-can-do-what). The dashboard only accepts *view*; alerts and reports only accept *manage*.
</Accordion>
<Accordion title="403 when approving a request">
You submitted it yourself. Another person with the *approve* permission must approve it.
</Accordion>
<Accordion title="404 for a letter of credit I can see in the list">
Export and import LCs use different IDs: export LC endpoints take `lcId`, import LC endpoints take `id`. See [Find your letters of credit](../04-products/13-letters-of-credit.md#find-your-letters-of-credit).
</Accordion>
<Accordion title="A date was refused, or comes back a day late">
Send dates as `DD/MM/YYYY`. Import LC and outward guarantee details return dates one day after the stored date; subtract one day. See [Working with the API](../04-products/12-trade-finance.md#working-with-the-api).
</Accordion>
<Accordion title="An approval timed out">
Read the record before trying again: if its status moved (for example to `applied`), the instruction was sent to the bank. Approving again could send it twice.
</Accordion>
<Accordion title="No webhook arrives for letters of credit or guarantees">
Trade finance sends emails, not webhooks. Poll the lists and details. See [Notifications](../04-products/12-trade-finance.md#notifications).
</Accordion>
</AccordionGroup>

## Still stuck?

Email your Multigate contact with the environment, endpoint, request time (UTC), `uniqueRequestId` or `transactionId`, and any reference ID from the error. Never send credentials or tokens.
