# Going live (/guides/going-live)



A test project (`mptest…` keys) rehearses everything with the chain simulated — deposits you trigger,
payouts that settle on their own — and lists native coins only; see [Test mode](/guides/test-mode)
for exactly what differs. A live project moves real money on the first call, so the list below is worth
walking rather than skimming.

<AppPicture name="api-keys" alt="The dashboard API keys screen with separate sample keys for storefront and wallet integrations." caption="The dashboard controls, shown with sample data." />

## Key hygiene [#key-hygiene]

Your API key is a bearer credential. Anyone holding it can act as your project within the key's kind and
scopes.

* **Server-side only.** Never in a browser bundle, a mobile app binary, a public repository, a CI log or a
  support ticket. A key that has ever been in any of those is compromised regardless of what happened
  since.
* **Shown once.** We store a peppered hash, never the value. Only the display handle (`mp3f9c1a77`) and
  the last four hex are kept, to tell keys apart.
* **Roll by overlap.** `POST /v1/api_keys/{id}/roll` (or the console) mints a replacement with identical
  grants and gives the old key a grace window, 24 hours by default. Deploy the new key, confirm traffic
  is flowing on it, and let the old one expire.
* **One key per environment, one per system.** Separate keys mean you can revoke one without touching the
  others, and the handle in your logs says which system made a call.
* **Revoke on suspicion, not on proof.** Revocation is instant and takes effect within a minute; minting
  a replacement takes seconds.

Keys are managed by the **signed-in owner in the console**, never with a key: a leaked key cannot mint
further keys, so its blast radius stays what that key could do.

## Kind, scope, signing [#kind-scope-signing]

Three fences, checked in that order:

1. **Kind** — `checkout`, `wallet` or `payout` — decides which routes a key may reach at all. The key your
   storefront carries cannot send funds anywhere.
2. **Scopes** narrow it further. A key without `payouts:create` cannot create a payout whatever its kind.
3. **Signing** is the third factor for money out: it must be on for the service, for your project and
   for that key. A `payout` key with signing off can read and prepare but never send. `GET /v1/ping` says
   which of the three is blocking in `signing_blocked_by`.

## Pinning a key to IP addresses [#pinning-a-key-to-ip-addresses]

Any key can be restricted to a list of addresses — exact IPs or CIDR blocks, v4 or v6, up to 32 — in the
console or with `PATCH /v1/api_keys/{id}`.

* The address judged is the one your server presents to us.
* Empty means no restriction; the first entry makes the list exclusive.
* An entry that does not parse is refused at save time, never stored.
* A refused call answers `403 ip_not_allowed`.

<Callout type="warn" title="Pin only where the egress address is stable">
  Functions on shared infrastructure often present a different egress address on every call, and a
  pinned key would fail there intermittently. Pin the keys held by servers (or a NAT gateway) with
  addresses you control; leave a key on unpredictable egress unpinned and rely on kind and scope.
</Callout>

## Webhooks before the first payment [#webhooks-before-the-first-payment]

* Register the endpoint, send a test event, and watch the delivery succeed — [Manage endpoints](/guides/manage-webhooks).
* Verify the signature over the **raw body** before trusting any field — [Verify a signature](/guides/verify-signature).
* Dedupe on `event.id`; order on `sequence`; return 2xx within 10 seconds and process afterwards.
* Fulfil on `deposit.confirmed` (or `invoice.paid`), never on `deposit.detected`, and reverse on
  `deposit.orphaned`.

## The checklist [#the-checklist]

<Steps>
  <Step>
    **Amounts stay strings** through your whole stack. Search your code for `Number(` and `parseFloat(` near
    anything that came from the API.
  </Step>

  <Step>
    **Decimals come from `GET /v1/chains`**, per asset per chain. The BSC 18-decimal case is the one to test.
  </Step>

  <Step>
    **`Idempotency-Key` on every `POST /v1/payouts`**, generated per intent and stored with the intent, so a
    retry after a timeout sends the same key.
  </Step>

  <Step>
    **The webhook endpoint verifies, dedupes and acks fast**, and your handler has a default branch for event
    types it does not know.
  </Step>

  <Step>
    **The payout key is pinned and held only by your backend**, and signing is on at all three levels — check
    `GET /v1/ping`.
  </Step>

  <Step>
    **Reconcile against the REST API**, not against the events you happened to receive. If an event and a
    `GET` disagree, the `GET` is right.
  </Step>
</Steps>
