Skip to content

How does a crypto payment processor limit hot wallet risk?

Sep 15, 2026 14 min read Paymos Tech Paymos Tech
An orange dotted path reaching the one gap in a low white enclosure and curving back outside it, a single small cube inside, two tall blocks standing apart untouched

TL;DR

A custodial processor signs payouts without waiting for a person, which is what makes the key hot and what makes a convincing request the real threat. The controls around it are worth the size of the hole each one leaves: a scoped credential stops the wrong key, not the right key in the wrong hands; a destination whitelist bounds where money goes and says nothing about how much or whose wallet is at the far end; a freeze holds new payouts and recalls nothing already broadcast. On Paymos the list is the load-bearing one, which is why the change is the Owner's by default, lives only in the dashboard, and has no API route.

A custodial processor holds a key that can move your balance. Nothing below changes that.

What the controls change is how many ways a transfer can be produced that the key will agree to sign, and each closes one route while leaving others open. A control named without its limit is worth less than one named with it: the limit is where you have to put a person.

The key is hot for an ordinary reason. A merchant hits send at two in the morning in a timezone where nobody at the provider is awake, and the transfer still has to be built, signed, and broadcast. Cold storage is the absence of that: a key that needs a human present cannot answer a payout request. Anyone paying out on demand is running a hot wallet under some name or other.

So the interesting question was never whether the key is online. It is what a request has to survive before the key ever sees it.

Why does the biggest theft on record not involve a broken key?

Because the transaction was signed by the people who were supposed to sign it.

On or about 21 February 2025 roughly $1.5 billion in virtual assets left Bybit, and five days later the FBI attributed the theft to North Korea.

A week after the theft the Safe Ecosystem Foundation described the route in its own statement: the attack on the Bybit Safe "was achieved through a compromised Safe{Wallet} developer machine", which resulted in "the proposal of a disguised malicious transaction".

Several people approved it. The multisig did its job on the wrong transaction, and the count of signatures was never the variable. That is the test to apply to everything below: what does this control do when the request already looks legitimate?

What does isolated signing isolate?

The signer from the surfaces a merchant or a payer can reach.

Outgoing Paymos transactions are signed away from the public web path, and a payout can only name a destination already approved by the merchant.

Isolation is a reachability property. It shortens the list of things that can put a request in front of the key — real work, and less than the phrase suggests, because it does nothing about a request arriving through the front door with a valid signature.

It does not divide the authority to sign either, and nothing here should be read as saying it does. Managed custody means the merchant decides where money goes and somebody else runs the key that sends it. Whether that trade suits a given business is a custody-model decision that comes before any control below.

A treasurer's next question has a short answer: a Paymos balance is not lent against and earns nothing for Paymos while it sits.

Which credential can move money?

Exactly one of the two, and the split is enforced rather than documented.

A merchant holds a Payment credential and a Payout credential per environment.

The Payment key's access covers invoices and payment channels; the Payout key's covers withdrawals and the reads a payout needs to validate. Neither set reaches into the other, so a payment key that leaks out of a plugin, a log, or a laptop cannot create a payout — it has no payout access to abuse.

The check runs in one place. Every command passes a single authorization step in the request pipeline, which weighs the caller's access against what the operation demands; a credential that falls short is refused there and never reaches the handler.

A scope check written per route is one somebody forgets on the route added next quarter.

Two conditions sit beside that. A Payout key must carry an IP allowlist, and authentication refuses one whose list is empty instead of reading empty as unrestricted; a Payment key cannot carry a list at all, because payment traffic is meant to be callable from wherever your customers are.

There is also no per-site or per-service key to issue: one active key of each type per environment, four in total, and revocation is terminal.

That belongs in an incident plan rather than a brochure: a leak cannot be contained by revoking the integration that leaked it, because every integration in that environment holds the same key.

What does the withdrawal whitelist restrict?

Where money can go. Not how much, not who asked, and not whether the address is still yours.

The mechanics are short enough to read in one place:

  • With nothing on the list, nothing leaves. An empty whitelist is not read as "no restriction" — it switches withdrawals off.
  • An entry is keyed by address and network group. Approving one EVM address covers Ethereum, BSC, Polygon, Arbitrum, Optimism, Base, Avalanche, and Plasma in a single stroke; Tron, TON, and Solana are groups of their own.
  • Four groups can be whitelisted and only four, which is why NEAR and Sui accept payments and cannot receive a payout at all.
  • Removing an entry revokes it rather than deleting it, so the trail of who could be paid, and when, survives.
  • One approval carries up to 1,000 addresses, a CSV import up to 1,000 rows per file, and an account holds at most 5,000 active entries.
  • None of it has an API route. The list lives in the dashboard, and a Payout key reads it to validate a destination without being able to add one.

The credential that moves money cannot widen the set of places it may move to. That separation survives a misconfiguration, because the route to do it over the API was never built.

Now the limits. A whitelist is a statement about direction and says nothing about ownership: a merchant may approve a customer's or a player's address on purpose and pay it, which is how payouts to end users work here.

An address approved in March for a wallet a departing contractor controlled is an approved address in September.

It says nothing about size either. Two account limits do apply to a payout — a ceiling on one payout's value and a cap on how many may be in flight at once — and the remaining count is shown in the payout form before anything is typed, rather than arriving as a rejection afterwards.

Those bound a movement; the whitelist bounds a direction. A payout can sit comfortably inside both and still go somewhere you would not have chosen this morning.

Who is allowed to add a destination?

Fewer people than can send money to one, deliberately.

Managing the whitelist sits with the account owner by baseline. A Finance user can execute payouts to destinations the owner approved and cannot approve one; an Admin has no payout permission by baseline at all.

NIST's glossary puts the principle in a sentence about payroll: no user should have enough privilege to misuse the system alone, and the person authorizing a paycheck should not be the one who can prepare it.

Baselines are a starting position, though, and extra permissions can be granted to individual members.

The separation holds as long as nobody grants it away and forgets, which makes the permissions screen part of this control.

The second layer at that moment is a step-up challenge, and it is not unconditional. A merchant user with neither TOTP nor a passkey is never challenged, because a step-up strengthens a factor that already exists and cannot demand one that does not.

An account with nobody enrolled has no challenge to fall back on. What holds either way is the list: nothing leaves for an address that is not on it, every addition is written into the audit trail, and an empty list means no payouts.

Where the challenge does fire it is bound to the action it approves, spent on use rather than left open on the account.

Note where it sits. The payout carries no challenge; the change to the list does. The door is guarded rather than each departure through it, which is the whole argument for keeping that list short.

What stops a payout that is already moving?

Progressively less, and the shape of that is not obvious in advance.

Cancellation is a window at the start, not a recall. It is open only before execution begins, on a payout still sitting at created, and a second cancel on one already cancelled answers 200 and changes nothing.

Past that point the transfer belongs to the chain, and the whitelist turns out to have been the only thing between the request and an irreversible transaction.

Freezes work above the individual payout. There is a global outbound freeze, and one for a single asset-and-network pair that can raise itself: when ledger and wallet drift apart by more than a configured tolerance, that pair stops without waiting for anyone to notice.

That is a safety mechanism doing its job: an accounting disagreement halts the thing the disagreement is about, before anyone has worked out which side is wrong.

Separately, a payout route for one asset on one network can be unavailable for a while. It is refused at the door instead of queued: the pair is absent from the payout picker, a request naming it is rejected on creation, and the balance stays untouched.

Refusals are cheap here by construction. Creating a payout moves the amount plus its network fee into a hold, and one that ends without going on-chain returns that hold whole, network fee included.

So is a re-run: external_order_id is unique per merchant, and repeating a create returns the existing payout instead of sending a second one — which covers a settlement job that died halfway and nothing about a request that is genuinely different.

Why does a failed payout say so little?

Because the alternative is a raw internal string landing on a merchant's screen.

The reason a payout failed is not shown to the merchant. What is shown comes from the status — a closed set of outcomes — and says the payout did not go out, that the hold is released, and where to ask.

The field behind it holds raw signer and RPC exception text, internal figures, and operator notes. A closed status at the display point, never a string from further down, is the pattern.

The cost lands on the merchant, and the missing reason is not the whole of it. A payout that does not go out sends no email and puts nothing in the bell, so a failure surfaces in the webhook or nowhere at all.

How long the rest of a payout takes is a question for a different page.

What should you ask a custodial processor?

Start with the balance that is doing nothing, then move to the people.

Is it lent, staked, or earning for the provider while it sits? Then: which role approves a destination, can that role also send to it, and what does the challenge on a list change do for an account where nobody enrolled a second factor? A good answer names a role and a challenge, not a team. Last, ask what you are told when a payout does not go out and through which channel — anyone answering "we email you at every step" has either built something we have not or has not read their own notification code.

One control carries more than the rest, and it is the list of approved addresses. It works in proportion to how short it is, and the moment to guard is the moment it changes — which is why on Paymos that change is the Owner's by default, recorded, has no API route, and is the one action asking for a second factor, while the payout it later authorizes asks for nothing.

Each control on a Paymos payout, and what it leaves open (September 2026)
ControlWhat it stopsWhat it does not stop
Isolated signingSigning reached from the surfaces merchants and payers touchA request that already looks legitimate by the time it arrives
Separate Payment and Payout keysA stolen payment key creating a payout — it holds no payout accessA stolen payout key paying an address already on the whitelist
IP allowlist on the payout keyThat key being used from an address the merchant never listedA request from inside the network the merchant did list
Withdrawal whitelistAny destination the merchant has not approved in advanceAn approved address that has stopped being the merchant's
Whitelist changes, Owner by defaultA Finance user approving the destination they then payAn Owner account somebody else is signed into
Step-up on a whitelist changeA live dashboard session adding an address unchallengedAnything at all until a second factor is enrolled
Asset-and-network freezeNew payouts on that pair while ledger and chain disagreeA transfer already broadcast to the network

Frequently asked questions

What is a hot wallet in a crypto payment processor?

The signing key that builds and broadcasts payouts, kept online because a payout is requested at any hour and nobody is waiting to approve it. A processor that pays out on demand runs one, whatever it is called.

Can a stolen API key move my balance?

Only a Payout key can create a withdrawal, and only to an address already on your whitelist. A Payment key carries no payout access at all, and a Payout key with no IP allowlist is refused at authentication.

What happens if my withdrawal whitelist is empty?

Nothing can leave. An empty list is not treated as "no restriction" — it disables withdrawals, so the first destination has to be approved before the first payout.

Does one whitelist entry cover every network?

An entry is keyed by address and network group. One EVM address covers all eight EVM payout networks in a single stroke, while Tron, TON, and Solana are separate groups. NEAR and Sui take payments and cannot be paid out to.

Can I manage the withdrawal whitelist over the API?

No. There is no Merchant API route for it — the list is managed in the dashboard. A Payout key can read the list to validate a destination and cannot add one.

When NOT to use processor-side payout controls

  • If your policy says every outbound transfer must be signed by your own treasury team, no control on somebody else's signer satisfies it. That is a custody decision rather than a configuration one.
  • If a different customer wallet is the destination on every payout, an approved-address list is the wrong shape of control for you — keeping it current becomes the job it was meant to remove.
  • If you are counting the step-up challenge as a defence you already have, check first that somebody has enrolled TOTP or a passkey. Without one there is no factor to challenge, and the list still changes.
  • If you need a second person to review each payout before it goes out, there is no approval queue to switch on. A payout is initiated and sent, so the review has to happen before the request.

Sources

  1. 1. FBI IC3 — North Korea Responsible for $1.5 Billion Bybit Hack (accessed 2026-09-15)
  2. 2. Safe Ecosystem Foundation — Statement, 28 February 2025 (accessed 2026-09-15)
  3. 3. NIST CSRC Glossary — separation of duty (accessed 2026-09-15)
  4. 4. Paymos — Security (accessed 2026-09-15)
  5. 5. Paymos — Create withdrawal (accessed 2026-09-15)

Last reviewed Sep 15, 2026

#security#withdrawals#custody#hot-wallet
Share