Authentication

The WebSocket API uses the same authentication mechanism as the REST (Web) API: every message you send must be signed. Unlike a public feed, there is no separate "log in" step — each subscribe, info, and ping message carries your credentials and a freshly computed signature. An unsigned or incorrectly signed message is rejected.

Credentials you need

You will use three values from your zerohash API key (visible in the dashboard when logged in):

CredentialPlaceholderWhere it goesNotes
Public keyAPI_PUBLIC_KEYThe key field of every messageSafe to send
PassphraseAPI_PASSPHRASEThe passphrase field of every messageSafe to send
Private / secret keyAPI_PRIVATE_KEYUsed to compute the signature onlyBase64-encoded, and never sent over the wire

How the signature works

The signature is an HMAC-SHA256 hash of a payload string, keyed by your base64-decoded private key, with the result base64-encoded.

Step 1 — Build the payload string

Concatenate these four components in this exact order (no separators):

payload = timestamp + method + route + json_body

ComponentValue
timestampCurrent Unix epoch time in seconds, as a string (e.g. "1720000000")
methodThe literal string "POST"
routeThe literal string "/"
json_bodyThe compact JSON string of the message body — no extra whitespace

Step 2 — Compute the HMAC

  1. UTF-8 encode the payload string to bytes.
  2. Base64-decode your API_PRIVATE_KEY — this is the HMAC key.
  3. Compute HMAC-SHA256(key, payload_bytes).
  4. Base64-encode the resulting digest → this is your signature.
⚠️

The values you sign must exactly match the values you send.

  • Use the same timestamp in the payload and in the message's timestamp field.
  • The json_body you sign must be byte-for-byte identical to the body you send — same key order, same whitespace. A different serialization produces a different signature and the request will be rejected.

Websocket Authentication


The message envelope

Every message you send — regardless of feed — shares the same envelope:

messageTypestringThe action: subscribe, info, or ping
topicstringbalances, prices, or available-symbols (context-dependent)
bodystringPayload / filter object (required; may be on the balances feed)
keystringYour API_PUBLIC_KEY
passphrasestringYour API_PASSPHRASE
timestampstringUnix epoch seconds — the same value used to compute the signature
signaturestringBase64-encoded HMAC-SHA256 signature

Example signed message:

{
  "messageType": "subscribe",
  "topic": "balances",
  "key": "API_PUBLIC_KEY",
  "passphrase": "API_PASSPHRASE",
  "timestamp": "1720000000",
  "signature": "base64_encoded_hmac",
  "body": { "filter": {} }
}

SymptomLikely cause
Message rejected / auth errorjson_body used for signing differs from the body sent (whitespace or key order), or timestamp mismatch between payload and field
Intermittent rejectionsClock drift — ensure timestamp is current system time in seconds, not milliseconds
Works in REST but not WSRemember the private key must be base64-decoded before use as the HMAC key

Next steps