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:

  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
  4. Sufficient balance: Users need adequate funds for minimum stake amounts

Core Concepts

Definitions

TermDefinition
EpochEthereum: 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 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 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 code
  • account_label (optional): Filter for a specific participant’s specific account
  • asset (optional): Filter by specific asset
  • page (optional): Page number for pagination
  • limit (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 identifier
  • account_identifier (required): Account URN for the staking operation
  • asset (required): Asset to stake
  • amount (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 from POST /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 asset
  • status (optional): Filter by status
  • page (optional): Page number
  • limit (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 asset
  • stake_id (optional): Filter by specific stake
  • start_date (optional): ISO 8601 date (2025-08-01)
  • end_date (optional): ISO 8601 date (2025-08-31)
  • page (optional): Page number
  • limit (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"  
}