# Go-live checklist

Work through this list before you ask Multigate to enable production. Each item prevents a problem we have seen in real integrations, and links to where it's explained. Tick an item only when you have done or tested it.

## Credentials and security

- [ ] **Generate a production API key.** Sandbox keys don't work in production. In the production dashboard ([multigate.app](https://multigate.app), **Developer Tool › API Configuration › API keys**), generate a key with a clear name (for example "Production – payouts server"). It is shown once. See [Get an API key](06-authentication.md#get-an-api-key).
- [ ] **Store the API key and the client secret in a secrets manager.** Load them at runtime; keep them out of code, config files, container images, logs and tickets. The client secret comes from the same page and verifies your webhooks. See [Your credentials](06-authentication.md#your-credentials).
- [ ] **Plan key rotation.** Decide how often you rotate (for example every 90 days, and at once when someone with access leaves or a key may have leaked), who does it, and test the steps once: generate the second key, deploy it, check **Last used**, revoke the old one. Your business can have only 2 active keys, so revoke old keys as soon as they are replaced. See [Rotate an API key](06-authentication.md#rotate-an-api-key).
- [ ] **Register your production server addresses on the IP allow-list.** Find each server's public outbound IP address, and add them in the production dashboard (**Developer Tool › API Configuration › IP Whitelist**) or with [Add or update a client configuration](api:createClientConfiguration). Then call [Get the current user](api:getCurrentUser) from each server: a missing address gets `403` "Unauthorized access". See [IP allow-list](06-authentication.md#ip-allow-list).
- [ ] **Alert on `401` instead of retrying.** With an API key, a `401` means the key is wrong or revoked and retrying won't help: raise an alert. See [When a request returns 401](06-authentication.md#when-a-request-returns-401).
- [ ] **Keep secrets and account numbers out of logs.** Search your logs for API keys (`mgk_live_…` and `mgk_sandbox_…`), passwords, client secrets and full account numbers, and mask them (show only the last 4 digits). Make sure your HTTP client doesn't log request headers.
- [ ] **Give your API user only the permissions it needs, and decide on approvals.** Check its permissions with [Get the current user](api:getCurrentUser). Decide with your administrator whether its payouts need a second person to approve them (`pending_approval`) or go straight to processing. See [Approvals](../04-products/05-payouts.md#approvals).

## Moving money safely

- [ ] **Send a `uniqueRequestId` on every money-moving request** (transfers, refunds, remittances and account requests; bulk payout rows get IDs from Multigate). Generate it once per business operation, make it unique across Multigate (for example a UUID), and store it **before** you call the API. See [Idempotency](../02-concepts/03-idempotency.md).
- [ ] **Treat a timeout, `5xx` or dropped connection as unknown, not failed.** Look the request up by its `uniqueRequestId` (for transfers, with [Find transfers](api:listTransfers)), and retry only with the **same** ID. See [Unknown outcomes](../02-concepts/02-errors.md#unknown-outcomes).
- [ ] **Pay from the right account.** Use the wallet's own account number (or its `accountId` as `sourceAccountId`), never a virtual account number. See [Which ID goes where](02-platform-overview.md#which-id-goes-where).
- [ ] **Check new beneficiaries before the first payment.** For NGN accounts, call [Validate a beneficiary account (NGN)](api:validateBeneficiaryAccount) or [Look up an account name](api:nameEnquiry), and show the returned name to your user. See [Beneficiaries](../04-products/04-beneficiaries.md#check-an-account).
- [ ] **Show the fee before your user confirms.** Use [Get a transfer fee](api:getTransferFee). See [Fees](../04-products/05-payouts.md#fees).
- [ ] **Never use floating-point numbers for money.** Keep amounts as decimal strings or integers in minor units (for example kobo) in your system. See [Amounts and currencies](../02-concepts/07-amounts-and-currencies.md).

## Webhooks

- [ ] **Register your production webhook URL before you create anything in production.** Events that happen while no URL is registered are lost. Use HTTPS with a valid certificate, reachable from the internet, with no redirects. See [Set up your endpoint](../03-webhooks-and-emails/01-overview.md#set-up-your-endpoint).
- [ ] **Verify the signature of every webhook** with your production client secret, and reject failures with `401`. See [Verifying signatures](../03-webhooks-and-emails/03-verifying-signatures.md).
- [ ] **Reply `2xx` within 5 seconds** (Multigate gives up after 9) and do the work afterwards, for example from a queue. See [How delivery works](../03-webhooks-and-emails/01-overview.md#how-delivery-works).
- [ ] **De-duplicate and accept any order.** Store each `Idempotency-Key` header you process, also compare the object's ID and status, and ignore events that would move a status backwards. See [Handle events safely](../03-webhooks-and-emails/01-overview.md#handle-events-safely).
- [ ] **Use your production server's URL,** not a development tunnel.
- [ ] **Alert on stuck events:** stored webhook events that stay unprocessed for more than 15 minutes.
- [ ] **Poll when no final webhook arrives.** A job checks every payout still `pending`, `pending_approval` or `processing` after your timeout, because some outcomes send no webhook. See [Outcomes without a webhook](../03-webhooks-and-emails/02-event-catalogue.md#outcomes-without-a-webhook).

## Operations

- [ ] **Log errors so support can trace them:** the HTTP status, the error body, the reference ID and your `uniqueRequestId`. See [Server errors and reference IDs](../02-concepts/02-errors.md#server-errors-and-reference-ids).
- [ ] **Alert on payouts stuck in a non-final status** longer than you expect. See [Transfer statuses](../04-products/05-payouts.md#transfer-statuses).
- [ ] **Reconcile daily:** match your records against account statements. See [Daily reconciliation](../04-products/10-balances-and-statements.md#daily-reconciliation).
- [ ] **Know how to reach Multigate support** and what to include. See [Support](01-introduction.md#support).
- [ ] **Watch the mailbox that receives Multigate emails.** Transfer emails go to the API user's email address; inflow emails go to your business contact. See [Who receives emails](../03-webhooks-and-emails/04-email-notifications.md#who-receives-emails).

## Testing in the sandbox first

:::warning[Passing in the sandbox isn't enough]
Some production checks are off in the sandbox. Read [Sandbox vs production](05-sandbox-vs-production.md) and run its [re-test list](05-sandbox-vs-production.md#before-go-live-re-test-against-production-rules) with production credentials on day one.
:::

- [ ] **Run every flow you'll use in production in the sandbox, including failures:** an invalid account, insufficient balance, a reused `uniqueRequestId`, a failed transfer webhook. See the [sandbox test checklist](04-environments-and-sandbox.md#test-checklist).
- [ ] **Test your signature check with a real sandbox webhook,** and confirm a tampered payload is rejected.
- [ ] **Keep environment differences in configuration only.** No `if sandbox` branches in your code, and no call to [Simulate an inflow](api:simulateInflow) in production code.
- [ ] **Use real email domains in test data.** Production refuses disposable ones such as yopmail.com. See [Email addresses](05-sandbox-vs-production.md#email-addresses).
- [ ] **Check the source balance before an FX lock and handle `402`.** The sandbox doesn't enforce it. See [FX conversions](05-sandbox-vs-production.md#fx-conversions).
- [ ] **Base production timeouts and alerts on production, not sandbox, timings.** The sandbox is slower. See [Payout results and timing](05-sandbox-vs-production.md#payout-results-and-timing).
