Introduction

zerohash offers comprehensive staking services that allow platforms to enable crypto staking for their users. This guide provides best practices for integration, explains the staking lifecycle, and offers guidance on building a seamless user experience.

The designs referenced in this documentation represent our conceptual SDK framework. For access to the complete design specifications and additional technical resources, please contact our integration team.

Please note this guide reflects our current beta implementation and is subject to change. As we continue enhancing our staking infrastructure, specifications may evolve to incorporate user feedback and platform improvements.

Prerequisites

Before enabling staking functionality, ensure your integration supports these requirements:

  1. KYC verification: Users must complete identity verification
  2. Jurisdiction compliance: Staking must be enabled for user's jurisdiction
  3. Terms acceptance: Users must accept staking terms and conditions

Core Concepts

Definitions

TermDefinition
Activation QueueNew validators must wait in a queue before becoming active. Queue length affects how long it takes for newly staked ETH to start earning rewards (typically 1-4 weeks).
Activation TimeThe delay between submitting a stake transaction and when the stake becomes active and starts earning rewards. Includes blockchain confirmation time plus any network-specific activation queues.
Unstake PeriodThe mandatory waiting period between initiating an unstake request and funds becoming available for withdrawal. This period varies by network.

Supported Assets

Currently supported cryptocurrencies with staking:

  • Ethereum (ETH)
  • Solana (SOL) - Late Q1 2026
  • Additional assets: Coming soon!

Staking Lifecycle and State Machine

The staking process follows a clear six-step journey from discovery to unstaking. We recommend using both API polling and webhook notifications to track a stake's status throughout this flow.

State Flow

  1. Submitted → Request accepted, queued for blockchain broadcast
  2. Broadcasted → Transaction sent to network
  3. Confirmed → On-chain confirmation received
  4. Staked → Terminal state; actively earning rewards
  5. Unstaked → Terminal state; no longer earning rewards or unstaking
  6. Canceled → Terminal state; user canceled before broadcast
  7. Failed → Terminal state; transaction rejected (requires new submission)

User Flow

1. Discovery and Initiation

Help users discover which assets they can stake and understand the terms.

🎨

UX Best Practices

  • Make the unstaking period impossible to miss - confusion on this can lead to high volumes of support issues
  • Highlight that APY estimates may fluctuate based on network conditions
  • Consider showing a simple ROI calculator for different stake amounts
  • For restricted jurisdictions, suppress all staking UI entirely

2. Eligibility and Compliance

Before allowing stake submission, confirm the user has accepted terms and has sufficient balance.

⚠️ Important: All staking operations are blocked until terms are accepted. Single acceptance enables staking for ALL supported assets.

🎨

UX Best Practices

  • Present terms acceptance as a one-time action that enables staking for all assets
  • Explain this is required for regulatory compliance
  • For users in restricted jurisdictions, do not surface any staking UX

3. Staking Execution

Once the user confirms their stake details, submit the request and begin tracking.

  • User confirms staking transaction details (amount, estimated reward APY, estimated unstaking period)
  • Submit stakes using POST /stakes
  • System validates request and returns stake ID for tracking - store the returned stake_id - you'll need it for future operations
  • Listen for stake.submitted webhook
  • Stake requests can be canceled before they have been broadcasted via POST /stakes/{stake_id}/cancel

⚠️ Important: Once broadcasted to the blockchain, cancellation is not possible. Users must wait for activation to complete, then unstake.

🎨

UX Best Practices

  • Show a clear confirmation screen with all terms before submission
  • Provide immediate visual feedback that submission succeeded
  • Make the cancel option obvious while it's still available and hide when unavailable

4. Activation and Monitoring

Monitor stake progress as it moves from submission to actively earning rewards.

  • Stakes move through several states, and real-time statuses will be provided via webhooks:
    • stake.submitted → Request accepted, queued for processing
    • stake.canceled→ User canceled a stake request before it was broadcasted
    • stake.broadcasted → Transaction sent to blockchain
    • stake.confirmed → Transaction confirmed on-chain
    • stake.staked → Actively earning rewards
    • stake.failed → Stake request failed and must be requested again
  • Monitor stake status via GET /stakes/{stake_id}/status

🎨

UX Best Practices

  • Show progress indicators during activation period
  • Explain that activation time varies by network conditions (1-4 weeks typical for ETH)
  • Notify users when their stake becomes active and starts earning
  • For failed stakes, clearly explain that the user needs to resubmit

5. Ongoing Management

Once active, rewards accrue daily and are automatically distributed to the user's balance.

🎨

UX Best Practices

  • Display total staked value, total rewards earned, and current estimated APY
  • Include rewards as distinct line items in transaction history sections
  • Allow users to view individual stakes or consolidated portfolio view

6. Unstaking

When users want to access their staked funds, guide them through the unstaking process and waiting period.

  • Check staked balances via GET /accounts?account_type=staked
  • Submit unstake requests using POST /stakes/unstake
  • Store unstake_id for tracking and support purposes
  • Unstakes move through several states, and real-time statuses will be provided via webhooks:
    • unstake.submitted → Request accepted, queued for processing
    • unstake.canceled→ User canceled an unstake request before it was broadcasted
    • unstake.broadcasted → Transaction sent to blockchain
    • unstake.confirmed → Transaction confirmed on-chain
    • unstake.unstaked → No longer staked, balance available to transact/trade
    • unstake.failed → Unstake request failed and must be requested again

🎨

UX Best Practices

  • Show unstaking period prominently before user commits
  • Explain that rewards continue to accrue until unstake reaches terminal unstaked state
  • Notify users when funds become available to trade or transact

Account Types and Ledger Balances

zerohash uses three distinct account types to track staking lifecycle states. Understanding how balances move between these accounts is essential for accurate balance reporting and transaction handling.

Account Type Definitions

account_typeDescriptionUse Cases
availableFunds that can be used to transact, trade, or stakeBuying, selling, transferring, withdrawing, or initiating new stakes
collateralFunds pending activation or pending unstaking periodMonitoring stakes during activation queue or unstaking waiting period
stakedFunds actively staked and earning rewardsTracking active staking positions and accrued rewards

Balance Flow Rules

During Staking:

  1. Funds move from availablecollateral when a stake is submitted
  2. Funds move from collateralstaked when the stake is confirmed and actively earning rewards

During Unstaking:

  1. Funds move from stakedcollateral when an unstake is submitted
  2. Funds move from collateralavailable when the unstaking period completes

Important Constraints

⚠️ Critical Balance Rules:

  • Balances in collateral account_type must reach the staked state before they can be unstaked
  • Balances in staked account_type must be unstaked before participants can withdraw or sell those assets
  • Only balances in the available account_type can be used for trading, transfers, or withdrawals

💡 Implementation Tip: Use GET /accounts?account_type={type} to query specific account types when displaying balances to users. Show all three account types in staking portfolio views to provide complete transparency.

Webhooks

Webhook Event Configuration

Set up webhook endpoints to receive real-time notifications for staking events. Configure your webhook URL and specify which events to receive.

{  
  "webhook_url": "<https://yourapi.com/webhooks/staking">,  
  "events": [   
  	"stake.submitted",
  	"stake.canceled",
  	"stake.broadcasted", 
  	"stake.confirmed",
  	"stake.staked",
  	"stake.failed",
  	"staking_reward.received", 
  	"unstake.submitted",
  	"unstake.canceled",
  	"unstake.broadcasted",
  	"unstake.confirmed",
  	"unstake.unstaked",
  	"unstake.failed"
	]  
}

Webhook Event Reference

stake.submitted

Sent immediately after stake request is accepted and queued for processing.

{  
  "event_type": "stake.submitted",  
  "event_id": "evt_123456789",  
  "timestamp": "2025-08-24T16:00:00.123Z",  
  "participant_code": "CUST01",  
  "stake_id": "c761fc96-5c44-40d4-8eb2-3fcd5d06754e",  
  "asset": "ETH",  
  "amount": "10.5",
  "notional": "35000.00",
  "notional_asset": "usd",  
  "status": "submitted"  
}

stake.confirmed

Sent when the staking transaction is confirmed on the blockchain and begins earning rewards.

{  
  "event_type": "stake.confirmed",  
  "event_id": "evt_234567890",  
  "timestamp": "2025-08-24T16:15:00.123Z",  
  "participant_code": "CUST01",  
  "stake_id": "c761fc96-5c44-40d4-8eb2-3fcd5d06754e",  
  "status": "confirmed"
 }

staking_reward.received

Sent when staking rewards are distributed and credited to the user's account.

{  
  "event_type": "staking_reward.received",  
  "event_id": "evt_345678901",  
  "timestamp": "2025-08-25T12:00:00.123Z",  
  "participant_code": "CUST01",    
  "reward_amount": "0.025",  
  "asset": "ETH",  
  "apy": "3.85",  
  "reward_date": "2025-08-25",
  "notional": "83.25",
  "notional_asset": "usd",
  "cumulative_rewards_amount": "0.125",
	"cumulative_rewards_notional": "100.00"  
}

unstake.submitted

Sent when an unstake request is submitted by the user.

{  
  "event_type": "unstake.submitted",
  "event_id": "evt_345678901",
  "participant_code": "string",
	"unstake_id": "c761fc96-5c44-40d4-8eb2-3fcd5d06757d",
  "asset": "ETH",  
  "amount": "10.5",
  "notional": "35000.00",
  "notional_asset": "usd",
  "status": "submitted"  
}

unstake.confirmed

Sent when the staking transaction is confirmed on the blockchain and begins earning rewards.

{  
  "event_type": "unstake.confirmed",  
  "event_id": "evt_234567890",  
  "timestamp": "2025-08-24T16:15:00.123Z",  
  "participant_code": "CUST01",  
  "stake_id": "c761fc96-5c44-40d4-8eb2-3fcd5d06754e",  
  "status": "confirmed"
 }

Implementation Best Practices

  • Acknowledge immediately: Always return 200 OK to acknowledge webhook receipt
  • Process asynchronously: Handle webhook processing outside the request/response cycle to avoid timeouts
  • Store payloads: Maintain webhook event logs for debugging and audit trails
  • Handle duplicates: Implement idempotency using event_id to prevent duplicate processing
  • Fallback strategy: Implement polling as a backup if webhook delivery fails
  • Verify authenticity: Validate webhook signatures to ensure requests come from zerohash
  • Monitor failures: Track webhook delivery failures and alert on persistent issues

Error Handling

Common validation errors and recommended handling:

  • Terms not accepted: Redirect user to T&C acceptance flow
  • Insufficient balance: Display current balance
  • Jurisdiction restrictions: Display appropriate messaging about availability
  • Asset not supported: Filter out non-stakeable assets in UI

Error Response Format

All error responses follow this structure:

{  
  "error_code": "ERROR_IDENTIFIER",  
  "message": "Technical error description",  
  "details": {  
    "field": "specific_field",  
    "value": "problematic_value",  
    "additional_context": {}  
  },  
  "user_message": "User-friendly error message",  
  "request_id": "req_123456789",  
  "timestamp": "2025-08-24T16:00:00Z"  
}

Common Error Codes

Validation Errors (400)

TERMS_NOT_ACCEPTED

{  
  "error_code": "TERMS_NOT_ACCEPTED",  
  "message": "User must accept staking terms before proceeding",  
  "user_message": "Please review and accept our staking terms and conditions"  
}

INSUFFICIENT_BALANCE

{  
  "error_code": "INSUFFICIENT_BALANCE",  
  "message": "Available balance insufficient for requested stake amount",  
  "details": {  
    "asset": "ETH",  
    "requested": "10.5",  
    "available": "8.2"  
  },  
  "user_message": "You need at least 10.5 ETH to complete this stake"  
}

JURISDICTION_RESTRICTED

{  
  "error_code": "JURISDICTION_RESTRICTED",  
  "message": "Staking not available in user's jurisdiction",  
  "details": {  
    "participant_code": "CUST01",  
    "jurisdiction": "NY"  
  },  
  "user_message": "Staking is not currently available in your location"  
}

Authentication Errors (401)

INVALID_TOKEN

{  
  "error_code": "INVALID_TOKEN",  
  "message": "Authentication token is invalid or expired",  
  "user_message": "Please log in again"  
}

Server Errors (500)

INTERNAL_SERVER_ERROR

{  
  "error_code": "INTERNAL_SERVER_ERROR",  
  "message": "An unexpected error occurred",  
  "user_message": "Something went wrong. Please try again later"  
}

Quick Reference

Essential Endpoints

Support and Resources

For additional assistance:

  • Integration support: Please contact your dedicated Relationship Manager
  • Design resources: Request FE design specifications