Start from what the integration does
A WooCommerce store creates payments and reads their status. It will never send a payout, never move a balance, never issue a refund. So why would its key be able to? Scoped keys make the capability list explicit: payment:create, payment:read, payout:create, payout:read, balance:read, transfer:create, refund:create — pick exactly the set one integration needs, mint a key with only those, and a call outside the set gets 403 insufficient_scope regardless of who's holding the key.
The three keys worth splitting in almost every deployment: the storefront key (payment:create + payment:read — the one most exposed, now harmless if lost), the payout key (payout:create + payout:read — lives only on the payout worker), and a read-only key for dashboards and reconciliation jobs that should never be able to move anything.
Then pin where it works from
Scopes bound what a key can do; the IP allowlist bounds where from. Attach CIDR ranges at mint time and the key simply doesn't authenticate from anywhere else — an attacker with your payout key and no foothold in your infrastructure has a string, not a capability. For server-side integrations with static egress this is the single highest-value restriction, and it costs one field.
Two more fences, same idea:
- Coin allowlist — a key restricted to
USDT_TRC20can't suddenly start creating BTC payouts. Useful when one integration legitimately only deals in one rail. - Per-key rate limit — cap a low-traffic integration at its real request rate, so a leaked key can't even be used fast.
Keys that expire, and keys that die with passwords
Set an expiry on anything handed to a contractor, an agency, or a proof-of-concept — access that ends by default beats access someone has to remember to revoke. Revocation itself is immediate and per-key, so killing the storefront key doesn't touch the payout worker.
There's also a fence you get without asking: when the account password is changed or reset, previously minted keys are invalidated wholesale. An attacker who phishes the dashboard password can't quietly keep API access through keys minted before the takeover was cleaned up — and conversely, cleaning up a takeover is one password reset, not a key-by-key audit under pressure.
Operational hygiene the platform does for you
- Plaintext once. The full key is shown at mint time and never again — the server stores only a hash. Listings show a short prefix so humans can tell keys apart without any key being retrievable.
- Lifecycle audit. Every create/revoke/rotate lands in an append-only event log per key, so "who minted this and when" is a lookup, not archaeology.
- Per-key webhooks. A key can carry its own webhook URL and secret — one integration's events go to its own endpoint, and rotating that secret touches nothing else. (This is also how brand-scoped keys route each storefront's events to its own backend.)
The five-minute policy
If you adopt nothing else: one key per integration, scoped to what it does, IP-pinned if it has a server, expiring if it has a contractor, and rotated when anyone who touched it leaves. None of these steps costs more than a minute in the dashboard, and together they change the blast radius of the inevitable leak from "attacker can drain payouts" to "attacker can create an invoice nobody will pay." With 2FA on the account, even payout-scoped keys can't move money without a fresh TOTP code — the last fence when every other one has failed.