Staking lets your users put their crypto to work — locking eligible assets to help secure a proof-of-stake network and earning rewards in return. zerohash runs the validator infrastructure behind the scenes, so your platform gets to offer native staking without operating a single node. You submit stakes and unstakes on behalf of your participants, zerohash handles the on-chain mechanics and reward distribution, and you apply your own platform fee on top.
For engineers and PMs, the thing to internalize up front is that staking is a lifecycle, not a transaction. A stake doesn't go live the instant you submit it, and it doesn't unwind instantly either — funds move through network queues that can take days or weeks. Your integration's job is to drive that lifecycle from your own systems and keep the user informed at every step: submit the request, track it through activation, surface rewards as they accrue, and manage the unstaking wind-down. Get the state handling right and the rest is straightforward.
Before you start
A participant has to clear a few gates before their first stake will go through:
- KYC — they must be a fully approved zerohash participant.
- Jurisdiction — staking isn't available in
CA,MD,NJ, andWA. Suppress the staking UI entirely for users in those states. - Terms — the participant must accept a signed agreement of
"type" : "staking". A single acceptance covers all supported assets. - Webhooks — stand up a signature-verified webhook endpoint to receive lifecycle events. Polling alone works, but webhooks are how you keep the experience real-time.
- Funding — they need enough balance in their available account to cover the stake.
How balances move
Staking is easiest to reason about through the three ledger account types a balance passes through:
- available — tradable, transferable funds. This is where a stake starts.
- collateral — a transition state: funds that are mid-activation or mid-unstaking, not yet in their final home.
- staked — the active, reward-earning position.
When a user stakes, funds flow available → collateral → staked. When they unstake, they flow back staked → collateral → available. Displaying these three buckets separately in your UI is the single biggest thing you can do for user clarity — it makes "why isn't my balance available yet?" answer itself.
The staking lifecycle
A stake moves through a well-defined set of states, and you'll track it via GET /stakes/{stake_id}/status and the matching webhooks:
- submitted — request accepted and queued for broadcast.
- broadcasted — signed and sent to the network.
- confirmed — accepted on-chain and sitting in the activation queue.
- staked — terminal state; the position is live and earning.
Two other outcomes exist: canceled (the user backed out before broadcast) and failed (the transaction was rejected and needs resubmission). You can cancel a stake with POST /stakes/{stake_id}/cancel, but only before it's broadcast. Once it's on-chain there's no undo — the user has to let it finish activating and then un-stake.
Activation takes timeBetween confirmed and staked there's a network activation queue — typically 1–4 weeks for ETH. The user earns nothing until the position reaches staked. Show a clear progress indicator during this window; it's the number-one source of "is my money stuck?" support tickets.
Rewards
Once a position is staked, rewards accrue daily and are credited automatically to the participant's staked balance. Each distribution fires a staking_reward.received webhook carrying a distribution_id — use that ID for idempotency so you never double-count a reward if an event is redelivered.
The economics are a gross-vs-net split: the network pays gross rewards to the position, your platform applies its configured platform fee (a percentage skimmed off the gross), and the remainder is credited to the user as net. The apy_net returned by the staking-info endpoint already reflects your fee, so it's the number to show users. Note that APY isn't fixed — it moves with network conditions, so present it as an estimate.
Unstaking
Unstaking mirrors staking. You submit with POST /stakes/unstake (referencing the stake_id, optionally for a partial amount), and the resulting unstake_id walks the same submitted → broadcasted → confirmed → unstaked path, with the same canceled/failed outcomes and the same pre-broadcast cancel option (POST /stakes/unstake/{unstake_id}/cancel).
The unstaking period — and a nice detailAfter confirmed, funds enter the network's exit queue and sit in a mandatory cooldown before landing back in available. Make this waiting period impossible to miss in your UI. The upside worth surfacing: users keep earning rewards throughout the unstaking period, right up until the funds transition to available.
A few constraints worth knowing
- No minimums — any amount a user holds can be staked or unstaked.
- No liquid staking — positions are illiquid; there are no receipt tokens to trade against a staked balance.
- Balances are gated by state — a staked balance must be unstaked before it can be withdrawn or sold, and collateral must finish activating before it can be unstaked.
- Common errors to handle gracefully — TERMS_NOT_ACCEPTED (route to the T&C flow), INSUFFICIENT_BALANCE (show current available), and JURISDICTION_RESTRICTED (hide staking for that user).