Staking
Introduction
Zerohash offers comprehensive staking services that allow platforms to enable crypto staking for their users. This guide provides a detailed overview of the staking API endpoints, staking lifecycle, and integration patterns.

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:
- KYC verification: Users must complete identity verification
- Jurisdiction compliance: Staking must be enabled for user's jurisdiction
- Terms acceptance: Users must accept staking terms and conditions
- Sufficient balance: Users need adequate funds for minimum stake amounts
Core Concepts
Definitions
Term | Definition |
---|---|
Epoch | Ethereum: A time period (approximately 6.4 minutes) consisting of 32 slots during which validators are assigned duties. Rewards are calculated and distributed at epoch boundaries. Solana: A period of approximately 2-3 days during which validator schedules are predetermined. Stake delegations become active at epoch boundaries. |
Activation Queue | New 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 Time | The 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 Period (Withdrawal Delay) | The mandatory waiting period between initiating an unstake request and funds becoming available for withdrawal. This period varies by network: - Ethereum: 8-12 days (due to withdrawal queue and processing delays) - Solana: 2-3 days (one epoch transition) No rewards are earning during this period and funds remain locked until the operation is complete. |
Supported Assets
Currently supported cryptocurrencies with staking:
- Ethereum (ETH): ~8 day unstaking period
- Solana (SOL): ~3 day unstaking period
- 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.

User Flow
The staking integration follows this typical user journey:
1. Discovery and Initiation
Your platform displays a list of available assets and their staking parameters.
- User views available stakeable assets via
GET /assets
- Platform displays staking information including APY, minimum amounts, and unstaking periods via
GET /assets/{asset}/staking_info
- User selects asset and desired stake amount
2. Eligibility and Compliance
Before proceeding, you must verify the user’s eligibility.
- System checks if user has accepted staking terms via
GET /participants/customers/{participant_code}
- If not accepted, user must accept staking T&C via
PATCH /participants/customers/{participant_code}
- Platform validates user has sufficient balance via
GET /accounts
3. Staking Execution
Once a user is eligible and ready, they submit the stake request.
- User confirms staking transaction details (amount, estimated rewards, unstaking period)
- Platform submits stake request via
POST /stake
- System validates request and returns stake ID for tracking
- Webhook notification sent when stake is submitted (
stake_submitted
)
4. Activation and Monitoring
After submission, the stake goes through several status changes before it is processed on chain.
- Staking provider processes transaction on blockchain
- Webhook notification sent when broadcasted (
stake_broadcasted
) - Network confirms transaction and staking begins
- Webhook notification sent when confirmed (
stake_confirmed
) - User can monitor status via
GET /stakes/{stake_id}/status
5. Ongoing Management
Daily reward distributions credited automatically
- Webhook notifications for each reward (
reward_received
) - User can view staking portfolio via
GET /stakes/{participant_code}
- User can track reward history via
GET /staking/{participant_code}/rewards
6. Unstaking
When a user wants to withdraw their funds, they initiate an unstake request.
- User views available unstakeable assets via
GET /assets
- User initiates unstaking via
POST /unstake
- System processes unstaking period (varies by asset)
- Funds become available after cooldown period completes
- Webhook notifications throughout unstaking process
End-to-end Staking Workflow
This example demonstrates the end-to-end process from checking user eligibility to monitoring active stakes.
Check User Eligibility and Accept Terms
Before any staking operations can be performed, verify that users have completed KYC and accepted the required terms.
Check Terms & Conditions Status
Endpoint: GET /participants/customers/{participant_code}
If the user has not accepted staking terms, they must do so using the below endpoint before any staking operations can proceed.
Accept Staking Terms and Conditions
If a user has not signed Staking terms and conditions, use this PATCH endpoint to update with the signed staking agreement.
Endpoint: PATCH /participants/customers/{participant_code}
Sample Request:
{
"signed_agreements": "staking"
}
Sample Response:
{
"participant_code": "CUST01",
"staking_terms_accepted": true,
"staking_terms_version": "v2.1",
"accepted_at": "2025-08-24T16:00:00Z"
}
Important: All staking operations are blocked until terms are accepted. Single acceptance enables staking for ALL supported assets.
Display Available Assets and Staking Parameters
Once eligibility is confirmed, display available stakeable assets and their current parameters. This information updates every 15 minutes.
Get Stakeable Assets
Endpoint: GET /assets
Sample Response:
{
"assets": [
{
"asset": "ETH",
"name": "Ethereum",
"rfq_liquidity_enabled": true,
"staking_enabled": true
},
{
"asset": "SOL",
"name": "Solana",
"rfq_liquidity_enabled": true,
"staking_enabled": true
}
]
}
Filter the response to show only assets where staking_enabled is true. Each asset will have different requirements and parameters.
Get Asset-Specific Staking Parameters
Endpoint: GET /assets/{asset}/staking_info
Sample Response:
{
"request_id": "req_123456789",
"message": {
"stakeable_assets": [
{
"asset": "ETH",
"apy_net": "3.47",
"lockup_period_days": 0,
"minimum_stake_amount": "1.00",
"payout_frequency": "daily",
"unstaking_period_days": "8"
}
],
"last_updated": "2025-08-24T15:30:00Z"
}
}
The apy_net represents the yield displayed to users (net of all fees), while unstaking_period_days shows the network-specific unstaking period.
Check Account Balances
Before allowing users to stake, verify they have sufficient available balance for the desired stake amount and minimum requirements.
Get Available Balances
Endpoint: GET /accounts?account_type=available
Query Parameters:
account_type
: Filter by account type (available, staked)account_owner
: For a specific participant or the platform’s participant codeaccount_label
(optional): Filter for a specific participant’s specific accountasset
(optional): Filter by specific assetpage
(optional): Page number for paginationlimit
(optional): Results per page
Sample Response:
{
"message": [
{
"asset": "ETH",
"account_owner": "CUST01",
"account_identifier": "urn:zh:us:accounts:customer:c761fc96-5c44-40d4-8eb2-3fcd5d06754e",
"account_type": "available",
"balance": "15.50000000",
"account_id": "acc_123456",
"last_update": 1693939200000
}
],
"pagination": {
"page": 1,
"total_pages": 1,
"total_records": 1
}
}
This endpoint should be utilized for both staking and unstaking operations to review available balances.
Execute Staking Operation
With eligibility confirmed and balances validated, submit the staking request. The platform performs additional validation checks before processing.
Submit Stake Request
Endpoint: POST /stake
Request Parameters:
participant_code
(required): User identifieraccount_identifier
(required): Account URN for the staking operationasset
(required): Asset to stakeamount
(required): Amount to stake
Sample Request:
{
"participant_code": "CUST01",
"account_identifier": "urn:zh:us:accounts:customer:c761fc96-5c44-40d4-8eb2-3fcd5d06754e",
"asset": "ETH",
"amount": "10.5",
"amount_notional": "35000.00",
"input_type": "usd",
"price_at_execution": "3333.33"
}
Success Response (200):
{
"request_id": "req_987654321",
"message": {
"stake_id": "stake_123456",
"participant_code": "CUST01",
"asset": "ETH",
"quantity": "10.5",
"price": "3333.33",
"notional": "34999.965",
"status": "submitted",
"estimated_monthly_reward_notional": "1347.50",
"estimated_monthly_reward_quantity": "0.3",
"estimated_activation_time": "2025-08-25T16:00:00Z",
"created_at": "2025-08-24T16:00:00Z",
"validator_info": {
"provider": "X",
"validator_id": "validator_abc123"
}
}
The successful response includes estimated rewards and activation timing. Store the stake_id for future operations.
Validation Error Response (400):
{
"error_code": "VALIDATION_FAILED",
"message": "Staking validation failed",
"details": {
"participant_code": "CUST01",
"asset": "ETH",
"validation_checks": {
"jurisdiction_allowed": true,
"terms_accepted": false,
"platform_restrictions": "none",
"sufficient_balance": true,
"meets_minimum_asset": true,
"meets_minimum_usd": true
}
},
"user_message": "Please accept staking terms and conditions to proceed"
}
The validation response shows which specific checks failed, allowing you to guide users to resolve issues.
Important: Store the
stake_id
returned fromPOST /stake
for future operations and status monitoring.
Monitor Stake Status
After submission, track the stake through its lifecycle. Use both polling and webhook notifications for comprehensive status tracking.
Check Individual Stake Status
Endpoint: GET /stakes/{stake_id}/status
Sample Response:
{
"request_id": "req_456789123",
"message": {
"stake_id": "stake_123456",
"participant_code": "CUST01",
"asset": "ETH",
"quantity": "10.5",
"current_quantity": "10.52",
"price": "3333.33",
"current_value_usd": "35068.40",
"status": "staked",
"apy": "3.85",
"total_rewards_earned": "0.02",
"last_reward_at": "2025-08-24T12:00:00Z",
"created_at": "2025-08-24T16:00:00Z",
"activated_at": "2025-08-25T16:00:00Z",
"validator_info": {
"provider": "X",
"validator_id": "validator_abc123"
}
}
}
The response shows both original quantity staked and current quantity (including earned rewards), along with validator information.
Get All User Stakes
Endpoint: GET /stakes/{participant_code}
Query Parameters:
asset
(optional): Filter by assetstatus
(optional): Filter by statuspage
(optional): Page numberlimit
(optional): Results per page
Sample Response:
{
"request_id": "req_321654987",
"message": {
"stakes": [
{
"stake_id": "stake_123456",
"participant_code": "CUST01",
"asset": "ETH",
"quantity": "10.5",
"current_quantity": "10.52",
"status": "staked",
"apy": "3.85",
"total_rewards_earned": "0.02",
"created_at": "2025-08-24T16:00:00Z",
"last_reward_at": "2025-08-24T12:00:00Z"
}
],
"consolidated_view": {
"total_staked_usd": "85000.00",
"total_rewards_earned_usd": "150.25",
"assets_staked": ["ETH", "SOL"],
"average_apy": "3.92"
}
}
}
Use the consolidated view for portfolio summaries and individual stakes for detailed position management.
Execute Unstaking Operation
When users want to unstake, they can choose partial or full unstaking. Unstaked funds enter an unstaking period before becoming available.
Submit Unstake Request
Endpoint: POST /unstake
Request Parameters:
unstake_type
: "partial" or "full"- Other parameters same as staking request
Sample Request:
{
"participant_code": "CUST01",
"account_identifier": "urn:zh:us:accounts:customer:c761fc96-5c44-40d4-8eb2-3fcd5d06754e",
"asset": "ETH",
"amount": "5.0",
"unstake_type": "partial"
}
Sample Response:
{
"unstake_id": "unstake_789012",
"stake_id": "stake_123456",
"participant_code": "CUST01",
"asset": "ETH",
"amount": "5.0",
"status": "submitted",
"cooldown_period_days": 8,
"estimated_available_at": "2025-09-03T16:00:00Z",
"created_at": "2025-08-24T16:00:00Z"
}
The response shows cooldown period and estimated availability. Please note that no rewards are earned during unstaking periods.
Get Reward History
Track all earned rewards with detailed history including USD values for tax reporting and portfolio analysis.
Retrieve Staking Rewards
Endpoint: GET /staking/{participant_code}/rewards
Query Parameters:
asset
(optional): Filter by assetstake_id
(optional): Filter by specific stakestart_date
(optional): ISO 8601 date (2025-08-01)end_date
(optional): ISO 8601 date (2025-08-31)page
(optional): Page numberlimit
(optional): Results per page
Sample Response:
{
"rewards": [
{
"reward_id": "reward_456789",
"stake_id": "stake_123456",
"asset": "ETH",
"reward_amount": "0.025",
"reward_date": "2025-08-23",
"apy_at_time": "4.22",
"validator_id": "validator_abc123",
"credited_at": "2025-08-24T12:00:00Z",
"usd_value": "83.25"
}
],
"summary": {
"total_rewards": "125.50",
"total_rewards_usd": "417.75",
"average_apy": "4.18",
"reward_frequency": "daily"
}
}
Use date range filters for specific reporting periods and the summary object for portfolio analytics.
Webhooks
Webhook 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://your-api.com/webhooks/staking">,
"events": [
"stake_submitted",
"stake_broadcasted",
"stake_confirmed",
"reward_received",
"unstake_submitted",
"unstake_confirmed",
"funds_available"
]
}
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": "stake_123456",
"asset": "ETH",
"amount": "10.5",
"status": "submitted",
"transaction_hash": null,
"estimated_confirmation_time": "2025-08-24T16:15:00Z"
}
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": "stake_123456",
"status": "staked",
"network_transaction_id": "0xabc123def456...",
"block_number": 18500000,
"validator_info": {
"provider": "X",
"validator_id": "validator_abc123",
"commission_rate": "5.0"
},
"activation_epoch": 185000
}
Reward Received
Sent daily when staking rewards are distributed and credited to the user's account.
{
"event_type": "reward_received",
"event_id": "evt_345678901",
"timestamp": "2025-08-25T12:00:00.123Z",
"participant_code": "CUST01",
"stake_id": "stake_123456",
"reward_amount": "0.025",
"asset": "ETH",
"apy_at_time": "3.85",
"reward_date": "2025-08-25",
"validator_id": "validator_abc123",
"usd_value": "83.25",
"cumulative_rewards": "0.125"
}
Error Handling
Common validation errors and recommended handling:
- Terms not accepted: Redirect user to T&C acceptance flow
- Insufficient balance: Display current balance and minimum requirements
- Below minimum stake: Show minimum stake amount for the asset
- 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"
}
BELOW_MINIMUM_STAKE
{
"error_code": "BELOW_MINIMUM_STAKE",
"message": "Stake amount below minimum threshold",
"details": {
"asset": "ETH",
"requested": "0.5",
"minimum_required": "1.0"
},
"user_message": "Minimum stake amount for ETH is 1.0 ETH"
}
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"
}
Updated about 4 hours ago