Skip to content

Learn

Exchange APIs

API signature methods compared: HMAC, RSA, Ed25519 and EIP-712

Figures on this page are as of 2026-09. Fees, limits and margin tiers change — check the venue's own docs.

A signing bug is almost never a cryptography bug. HMAC-SHA256 is the same function everywhere; what differs is the string fed into it, the key type, and how the output is encoded. Each venue makes those three choices independently, which is why a signer that works on one exchange produces a well-formed, confidently wrong signature on the next.

This page is the side-by-side reference for developers writing or porting a signer. For the underlying idea, start with what HMAC request signing is. Here the question is narrower: what exactly does each venue sign, and where does each one trip people up?

The matrix

VenueString to signAlgorithmsOutput encodingSent in
Binance SpotQuery string concatenated, with no separator, to the HTTP body; non-ASCII percent-encoded firstHMAC-SHA256, RSA, Ed25519 (Ed25519 recommended)HMAC: hex, not case-sensitive. RSA and Ed25519: case-sensitivesignature parameter; key in X-MBX-APIKEY
Binance USDⓈ-MtotalParams: query string concatenated with the bodyHMAC SHA256; RSA (RSASSA-PKCS1-v1_5, SHA-256). Ed25519: we did not find it on the futures General Info page“The signature is not case sensitive.”As Spot
Bybit V5timestamp + api_key + recv_window + queryString (GET) or + jsonBodyString (POST)HMAC_SHA256 (system-generated keys); RSA_SHA256 (self-generated keys)HMAC: lowercase hex. RSA: base64X-BAPI-SIGN
OKX V5timestamp + method + requestPath + bodyHMAC-SHA256Base64OK-ACCESS-SIGN, plus key, timestamp and passphrase headers
HyperliquidEIP-712 typed data, under one of two schemes (below)Ethereum (secp256k1) key; no HMAC, no API secretEthereum signatureSee Hyperliquid’s signing page
AlpacaNothing is signedNone; key ID and secret sent as headers, or HTTP Basic—APCA-API-KEY-ID, APCA-API-SECRET-KEY
IBKR (TWS API)Nothing is signedNone; a socket to a logged-in TWS or IB Gateway——

On key types: Binance’s spot docs list HMAC, RSA and Ed25519. Bybit’s list system-generated (HMAC) and self-generated (RSA) keys. OKX’s authentication section describes HMAC-SHA256. We did not find Ed25519 in Bybit’s or OKX’s docs, or on Binance’s futures General Info page, so do not assume a spot Ed25519 key works on Binance futures without checking.

Where the venues actually differ

Binance: the body is glued to the query, and case depends on the key

Binance’s payload rule, verbatim, is “the query string concatenated without separator to the HTTP body. Any non-ASCII character must be percent-encoded before signing.” Two consequences follow. Adding a & between query and body breaks the signature. So does signing a symbol or parameter containing non-ASCII text before encoding it.

Case sensitivity depends on key type: HMAC signatures are not case-sensitive, RSA and Ed25519 signatures are. A helper that upper-cases hex output is harmless with HMAC and fatal after switching to Ed25519, which Binance recommends “as it should provide the best performance and security out of all supported key types”. The failure is -1022, “Signature for this request is not valid.” — see Binance API error -1022.

Bybit: the key and the window are inside the signature

Bybit’s pre-hash starts with the timestamp, the API key and the receive window, then appends the query string or JSON body. Three of the values you send as headers are therefore also signed material, and each must match its header exactly — a header rebuilt after signing is a mismatch.

Encoding follows the key type: HMAC produces lowercase hex, RSA produces base64. Switching key type without switching encoding produces a valid signature in the wrong format. With self-generated RSA keys you give Bybit only the public key; Bybit says “we will never hold your private key”, and publishes an RSA key generator. The failure is 10004, “Error sign, please check your signature generation algorithm.” — see Bybit API error 10004.

OKX: method and path are signed, and REST differs from WebSocket

OKX signs the timestamp, the HTTP method, the request path and the body, then Base64-encodes the HMAC. The details that matter:

  • The method must be upper-case.
  • For GET, query parameters “are counted as requestpath, not body” — the docs’ own example signs /api/v5/account/balance?ccy=BTC.
  • The body is omitted when there is none, and bodies must be application/json.
  • The timestamp in the pre-hash is the same value as the OK-ACCESS-TIMESTAMP header.

WebSocket login is signed differently: the timestamp is Unix epoch seconds, the method is always GET, and the path is always /users/self/verify. A REST signer reused for WebSocket login fails. The passphrase travels as its own header and is not part of the pre-hash — see the OKX passphrase. A mismatch returns 50113, “Invalid signature.” — see OKX API error 50113.

Hyperliquid: two EIP-712 schemes

Hyperliquid’s signing page names the most common error first: not realising there are two schemes, sign_l1_action and sign_user_signed_action in the official Python SDK.

L1 actionsUser-signed actions
Used forOrders, cancels, leverage settings, vaultTransferapproveAgent, withdraw3, usdSend, spotSend and similar
What is signedA “phantom agent” {source, connectionId}; connectionId hashes the msgpack-encoded action, the nonce, the vault address and an optional expiresAfterThe action object itself, as typed data
EIP-712 domainname: "Exchange", chainId: 1337name: "HyperliquidSignTransaction", chainId from the action’s signatureChainId
Network markersource: "a" mainnet, "b" testnethyperliquidChain: "Mainnet" or "Testnet"

The rest of the official common-error list is about serialisation: msgpack field order matters, trailing zeroes on numbers must be removed, and addresses should be lower-cased before signing. The docs also warn against trusting a local recover-signer check.

A wrong signature does not produce a “bad signature” error. The exchange recovers a different address from the payload and reports that it does not exist: “L1 error: User or API Wallet 0x0123… does not exist.” In the docs’ words, “An incorrect signature does not indicate why it is incorrect.” If the address in the error changes between requests, it is a serialisation bug — see the “does not exist” error.

The official Python SDK is the reference implementation the docs point to. The TypeScript and Rust SDKs are community-written.

Alpaca and IBKR: nothing to sign

Alpaca sends the key ID and secret in two headers, or as HTTP Basic credentials; its docs now label this the “Legacy” flow, and its OAuth client-credentials flow “is not yet available for Trading API”. There is no pre-hash, so the whole class of construction and clock bugs is absent. The trade-off is that the secret itself travels on every request, protected by TLS, so where it is stored and logged matters more.

IBKR’s TWS API has no credential at the API layer. Authentication is a human logging into TWS or IB Gateway; the API is a local socket to that session.

What to do in code

Write one signer per venue. A generic “exchange signer” with per-venue flags is where ported constructions hide. The four signing venues share almost nothing beyond the hash function.

Build once, sign, send the same bytes. Serialise the query and body into variables, sign those variables, and send those variables. Most signature failures are a second serialisation.

Pin a test vector. Reproduce the venue’s own worked example — OKX’s signed /api/v5/account/balance?ccy=BTC, or the output of Hyperliquid’s official Python SDK for a fixed action — as a unit test, so a refactor that changes the pre-hash fails in CI rather than in production.

Log the pre-hash, never the secret. Comparing the signed string with what went on the wire resolves most failures in minutes.

Treat signature errors as permanent. -1022, 10004, 50113 and Hyperliquid’s “does not exist” will not clear on retry. Fail loudly — see authentication errors across exchanges.

FAQ

Which exchanges support Ed25519 API keys?

Binance’s spot API lists HMAC, RSA and Ed25519 keys and recommends Ed25519. We did not find Ed25519 on Binance’s USDⓈ-M futures General Info page, which documents HMAC and RSA, or in Bybit’s docs (HMAC and RSA) or OKX’s authentication section (HMAC-SHA256). Check the venue’s current docs before generating a key.

Why does Bybit use hex while OKX uses Base64?

Each venue chose its own output encoding. Bybit encodes HMAC signatures as lowercase hex and RSA signatures as base64; OKX Base64-encodes its HMAC; Binance encodes HMAC as hex. Sending the right signature in the wrong encoding fails exactly like a wrong signature.

Does Hyperliquid use API keys and HMAC?

No. Hyperliquid actions are signed as EIP-712 typed data with an Ethereum key, under one of two schemes: L1 actions for trading and user-signed actions for things like withdrawals and agent approval. A master account delegates signing by approving an API wallet rather than issuing a key and secret.

Can I reuse my Binance signing code for Bybit or OKX?

No. Binance signs the query string plus body; Bybit prepends the timestamp, API key and receive window; OKX signs the timestamp, method, path and body and Base64-encodes the result. Each construction produces a signature the other venues reject, so rebuild the signer from the target venue’s documentation.