---
title: "Crypto Exchange SDK: JS, TS, & Node.js Engineering Guide"
description: "Build resilient trading systems with a crypto exchange SDK for JavaScript, TypeScript, and Node.js. Reduce auth boilerplate and implement awaitable WebSockets."
canonical: "https://siebly.io/blog/crypto-exchange-sdk-js-ts-nodejs-engineering-guide"
---

# Crypto Exchange SDK: JS, TS, & Node.js Engineering Guide

Build resilient trading systems with a crypto exchange SDK for JavaScript, TypeScript, and Node.js. Reduce auth boilerplate and implement awaitable WebSockets.

## Technical Disclaimer

These articles are software engineering references for exchange API integrations. They are not financial, investment, legal, tax, compliance, or trading advice. Use public data, demo, testnet, or paper workflows first. Keep API credentials out of frontend code and disable withdrawal permissions for automation keys.

## Overview {#overview}

Signing requests yourself, then keeping a WebSocket alive, is where most exchange integrations start to rot. Each venue uses a different signature, a different clock or nonce rule, and a different shape for the same idea of "place an order". The [Siebly SDKs](/sdk) are separate Node.js clients, one package per exchange, written in TypeScript. They sign the request and keep the socket up. They do not throttle you. That part stays in your process.

## Key Takeaways {#key-takeaways}

- Request signing is not one HMAC recipe. Kraken, Gate, Binance, and Coinbase Advanced Trade do not sign the same way.
- Bybit's current client is V5 (`RestClientV5`). `SpotClientV3` is deprecated.
- Awaitable order calls over WebSocket exist on some of these packages, and not on Coinbase or BitMart.
- None of these SDKs queue or slow your requests to stay under an exchange rate limit. [binance](/sdk/binance/javascript) does record the weight headers Binance sends back.



## Signing is different on every venue {#signing-is-different-on-every-venue}

Exchange docs describe the HTTP API. They are not a client. If you sign by hand, the bugs show up as rejected private calls, and the reason is usually the signature string, not the order itself.

These are the schemes the current packages implement:

- [binance](/sdk/binance/javascript) signs REST with HMAC-SHA256, RSA, or Ed25519. A PEM private key is detected and switches the algorithm. The default `recvWindow` is 5000 ms. Time sync exists, and it is off unless you set `disableTimeSync: false`.
- [bybit-api](/sdk/bybit/javascript) V5 signs with HMAC-SHA256, or RSA-SHA256 when the secret is a PEM private key. Time sync is off unless you set `enable_time_sync`.
- [okx-api](/sdk/okx/javascript), [bitget-api](/sdk/bitget/javascript), and [kucoin-api](/sdk/kucoin/javascript) use HMAC-SHA256 plus the API passphrase. That passphrase is not your login password.
- [gateio-api](/sdk/gate/javascript) uses HMAC-SHA512.
- [@siebly/kraken-api](/sdk/kraken/javascript) hashes the payload with SHA-256, then signs with HMAC-SHA512. The API secret is base64. Spot signing uses a nonce.
- [@siebly/htx-api](/sdk/htx/javascript) uses HMAC-SHA256.
- `bitmart-api` uses HMAC-SHA256 over `timestamp#memo#params`. You pass `apiMemo` from the key you created.
- [coinbase-api](/sdk/coinbase/javascript) is split by product. Advanced Trade and the Coinbase App sign a JWT, and the client accepts ECDSA or Ed25519 keys. Coinbase Exchange, International, and Prime use HMAC-SHA256 plus a passphrase.

A clock that is off by a few hundred milliseconds is not a universal failure mode. Binance and Bybit reject a request when the timestamp falls outside `recvWindow` (5000 ms on the Binance client unless you change it). Kraken spot fails on a bad nonce, not on that same window. Sync the host clock first. If a Binance or Bybit call still reports a recvWindow error, both WebSocket API clients expose `setTimeOffsetMs()`.

Bybit's old spot V3 client is still exported as `SpotClientV3`, and the source marks it deprecated. Those endpoints were scheduled to be turned off by the end of 2024. New code should use `RestClientV5`. The V5 methods take a `category` (`spot`, `linear`, `inverse`, `option`) because that is how Bybit's Unified Trading Account API is shaped. Calling that "Unified Margin" is the older account name, and it is the wrong one to use against this client.

## What the SDK is responsible for {#what-the-sdk-is-responsible-for}

Each package splits stateless REST calls from the WebSocket client that holds the connection, heartbeats, and reconnect.

The SDK signs, stamps the timestamp or nonce, and parses the JSON into the typed method you called. It does not decide how fast you are allowed to call. A 404 is a missing path or resource. Rate limits come back as 429, or as an exchange error code in the body. Do not treat those as the same failure.

On [binance](/sdk/binance/javascript), the REST client stores the weight and order-count headers from the last response on `apiLimitTrackers` (`x-mbx-used-weight-1m`, `x-mbx-order-count-10s`, and the other `x-mbx-*` counters). That is a reading, not a limiter. Nothing in that client waits, queues, or sheds a call when the weight gets high. If you need a queue, you write it.

`binance` and [bybit-api](/sdk/bybit/javascript) publish a CommonJS build (`main` points at `lib/index.js`). Node can `import` that build, and the examples also show `require()`. The other packages publish both an ESM and a CJS entry (`import` and `require` in `exports`).

## One package per exchange {#one-package-per-exchange}

A single wrapper that pretends every exchange has the same `createOrder()` will hide the parameters you actually need. Bybit V5 wants `category`. Binance Portfolio Margin is a separate client, `PortfolioClient`, next to `MainClient` (spot, cross margin, isolated margin, and the other main-site products), `USDMClient`, and `CoinMClient`. Vanilla options are not in the Binance REST clients yet.

Bitget's awaitable WebSocket orders are the V3 / Unified Trading Account API. `RestClientV2` and `WebsocketClientV2` are still in the package for the classic account. `WebsocketAPIClient` on Bitget expects V3 keys.

The npm names below are the ones in each `package.json`. Kraken and HTX are scoped. The others are not.

| Package | REST clients | Awaitable WebSocket API |
| --- | --- | --- |
| [binance](/sdk/binance/javascript) | `MainClient`, `USDMClient`, `CoinMClient`, `PortfolioClient` | Yes, spot and futures. Ed25519 can log the session in once. HMAC and RSA sign every command. |
| [bybit-api](/sdk/bybit/javascript) | `RestClientV5` | Yes, V5. Demo trading can consume events. It does not support this WebSocket API. |
| [okx-api](/sdk/okx/javascript) | `RestClient` | Yes. Passphrase required. |
| [bitget-api](/sdk/bitget/javascript) | `RestClientV2`, `RestClientV3` | Yes, V3 / UTA only. Passphrase required. |
| [gateio-api](/sdk/gate/javascript) | `RestClient` | Yes, spot and futures. |
| [kucoin-api](/sdk/kucoin/javascript) | `SpotClient`, `FuturesClient`, `UnifiedAPIClient`, `BrokerClient` | Yes, spot, margin, and futures. Passphrase required. |
| [@siebly/kraken-api](/sdk/kraken/javascript) | `SpotClient`, `DerivativesClient`, `InstitutionalClient`, `PartnerClient` | Spot only. |
| [coinbase-api](/sdk/coinbase/javascript) | `CBAdvancedTradeClient`, `CBAppClient`, `CBExchangeClient`, `CBInternationalClient`, `CBPrimeClient`, `CBCommerceClient` | No. Streams only, through `WebsocketClient`. |
| `bitmart-api` | `RestClient`, `FuturesClientV2` | No. Streams only, through `WebsocketClient`. |
| [@siebly/htx-api](/sdk/htx/javascript) | `SpotClient`, `FuturesClient` | Yes, spot and derivatives. |

Specialized clients are the right tool when you place or amend orders. A unified library is fine for a read-only screen that only needs last price from many venues. When Bybit or Binance changes a parameter, the matching package can ship that change on its own. A wrapper that maps ten exchanges through one method waits on every other venue first.



## REST and WebSocket calls {#rest-and-websocket-calls}

Use REST for snapshots and for anything the venue does not expose as a request/response socket call. Use the WebSocket client for streams. Where the table above says the WebSocket API exists, `WebsocketAPIClient` returns a Promise for that call. You await the matching response. You do not have to correlate ids yourself.

Public REST needs no keys. This is the Bybit V5 ticker call from the SDK examples. `category` is required, and it is how you pick the V5 product:

```ts title="Imported example"
import { RestClientV5 } from "bybit-api";

const client = new RestClientV5();

const tickers = await client.getTickers({ category: "linear" });
console.log(tickers);
```

The same pattern on Binance spot uses `MainClient`. Credentials are optional until you call a private method:

```ts title="Imported example"
import { MainClient } from "binance";

const client = new MainClient();

const ticker = await client.get24hrChangeStatistics({
  symbol: "BTCUSDT",
});
console.log(ticker);
```

Bybit's WebSocket API client is the same V5 surface, as a Promise. The snippet below is the SDK's own order example. With live keys this sends a real limit order. `testnet: true` is available on this client. Demo trading is not: as of the SDK example notes (Jan 2025), Bybit demo trading can consume events and does not accept WebSocket API orders.

```ts title="Imported example"
import { WebsocketAPIClient } from "bybit-api";

const wsClient = new WebsocketAPIClient({
  key: process.env.API_KEY_COM,
  secret: process.env.API_SECRET_COM,
  // testnet: true,
});

const response = await wsClient.submitNewOrder({
  category: "linear",
  symbol: "BTCUSDT",
  orderType: "Limit",
  qty: "0.001",
  side: "Buy",
  price: "50000",
});
console.log(response);
```

Binance has the same style of client. HMAC and RSA work, and each command is signed on its own. Ed25519 is the one that can authenticate the socket up front, which is why the SDK examples call it the faster path for this API. Pass the Ed25519 private key as `api_secret`.

```ts title="Imported example"
import { WebsocketAPIClient } from "binance";

const wsClient = new WebsocketAPIClient({
  api_key: process.env.API_KEY_COM,
  api_secret: process.env.API_SECRET_COM,
});

const response = await wsClient.submitNewSpotOrder({
  symbol: "BTCUSDT",
  side: "SELL",
  type: "LIMIT",
  timeInForce: "GTC",
  price: "23416.10000000",
  quantity: "0.00847000",
});
console.log(response);
```

OKX will reject the call if you only pass a key and secret. The REST and WebSocket API clients expect the passphrase on the account object:

```ts title="Imported example"
import { WebsocketAPIClient } from "okx-api";

const wsClient = new WebsocketAPIClient({
  accounts: [
{
apiKey: process.env.API_KEY_COM || "",
apiSecret: process.env.API_SECRET_COM || "",
apiPass: process.env.API_PASSPHRASE_COM || "",
},
  ],
});

const res = await wsClient.submitNewOrder({
  instId: "BTC-USDT",
  tdMode: "cash",
  side: "buy",
  ordType: "limit",
  px: "10000",
  sz: "0.001",
});
console.log(res);
```

The place-order type marks `instId` as deprecated since March 2026 in favor of `instIdCode` (a numeric instrument id, which wins if you send both). The SDK examples still pass `instId`. Check OKX's current instrument mapping before you rely on the string id.

Bitget's version of that call is `submitNewOrder('spot', {... })` on `WebsocketAPIClient`, and it needs `apiKey`, `apiSecret`, and `apiPass` from a V3 key. KuCoin uses `apiPassphrase` and methods such as `submitNewSpotOrder`. Gate uses `submitNewSpotOrder` and also has futures methods on the same client. Kraken's `submitSpotOrder` is spot only. HTX's `submitSpotOrder` needs the spot `account-id` from your account.

Keep the process that places orders separate from the process that chews through order book diffs. The socket client can stay current and still be useless if your parser blocks the event loop. Public data and testnet or demo, where that venue actually supports it, should be the first place these calls run. See [historical and live data pipelines](/ai/historical-live-data-pipeline) if you are splitting ingest from execution.

## Reconnects, and where await does not exist {#reconnects-and-where-await-does-not-exist}

The stream clients emit `reconnect` and `reconnected` and resubscribe after the socket returns. That covers Binance (including the 24 hour disconnect and listenKey refresh), Bybit, OKX, Bitget, Gate, KuCoin, Kraken, HTX, Coinbase, and BitMart. A reconnect does not rewind the trades you missed. Snapshot the book or the account over REST when the socket comes back, then continue from the stream.

Coinbase and BitMart do not have `WebsocketAPIClient`. You cannot `await` an order acknowledgement on those two. Coinbase Advanced Trade public data looks like this:

```ts title="Imported example"
import { WebsocketClient } from "coinbase-api";

const client = new WebsocketClient();

client.on("update", (data) => {
  console.log(data);
});

client.on("reconnected", (data) => {
  console.log("reconnected", data?.wsKey);
});

client.subscribe(
  {
topic: "ticker",
payload: {
product_ids: ["BTC-USD"],
},
  },
  "advTradeMarketData",
);
```

Private Coinbase streams use the same `subscribe()` call with a user-data `wsKey` such as `advTradeUserData`. The client authenticates that connection when keys are present. Order placement for Coinbase stays on the REST client, for example `CBAdvancedTradeClient`.

BitMart is the same shape: `new WebsocketClient()`, listen for `update`, and subscribe. There is no promise wrapper for trading commands.

## Typed clients and coding agents {#typed-clients-and-coding-agents}

The request objects you pass are the types checked by the compiler. That is what an editor, and a coding agent, can follow. [coinbase-api](/sdk/coinbase/javascript) types most requests and responses, not every field of every payload, so do not assume a missing property is a bug in your code before you check the type.

The [Siebly AI Prompt Framework](/ai) and the [pattern notes](/ai/patterns) are written against these clients. Point the agent at the package and the method, not at a generic "exchange" interface. When an API version moves, track it on [releases](/releases) for the package you actually depend on.

These clients are a signing and transport layer. They are not a trading system, and they are not a rate limiter. Build the throttle, the order state, and the kill switch yourself.

The packages in the table above are the current set: [binance](/sdk/binance/javascript), [bybit-api](/sdk/bybit/javascript), [okx-api](/sdk/okx/javascript), [@siebly/kraken-api](/sdk/kraken/javascript), `coinbase-api`, [bitget-api](/sdk/bitget/javascript), [gateio-api](/sdk/gate/javascript), [kucoin-api](/sdk/kucoin/javascript), `bitmart-api`, and [@siebly/htx-api](/sdk/htx/javascript). Install docs and per-exchange tutorials start at [siebly.io/sdk](/sdk).

## Frequently Asked Questions {#frequently-asked-questions}

### Does the Siebly SDK handle exchange rate limits automatically? {#does-the-siebly-sdk-handle-exchange-rate-limits-automatically}

No. None of these clients delay or queue calls to stay under a venue limit. On [binance](/sdk/binance/javascript), read `apiLimitTrackers` if you want the latest `x-mbx-used-weight-*` and order-count headers. You still have to act on them. Other venues expect you to watch their own headers or error codes and back off in your own queue.

### How do I securely sign API requests in a Node.js environment? {#how-do-i-securely-sign-api-requests-in-a-node-js-environment}

You don't sign them yourself. Construct the client with the key material for that venue and call the method. Load keys from the environment, keep them on the server, and create the key without withdraw permission unless you have a separate reason to move funds.

The algorithm depends on the package. See the list in Signing is different on every venue. OKX, Bitget, and KuCoin need the API passphrase. BitMart needs `apiMemo`. Coinbase Advanced Trade needs the key type the JWT signer expects (ECDSA or Ed25519), not a raw HMAC secret.

### What is the advantage of an awaitable WebSocket for order placement? {#what-is-the-advantage-of-an-awaitable-websocket-for-order-placement}

On the packages that implement it, `WebsocketAPIClient` sends the command on the open socket and resolves when the matching response arrives. That is one round trip on the connection you already authenticated, instead of a separate REST call.

It is not available on [coinbase-api](/sdk/coinbase/javascript) or `bitmart-api`. On [@siebly/kraken-api](/sdk/kraken/javascript) it is spot only. On [bitget-api](/sdk/bitget/javascript) it is V3 / UTA only. On [bybit-api](/sdk/bybit/javascript) it is V5, and it is not available in Bybit demo trading.

### Can I use these SDKs for high-frequency trading (HFT)? {#can-i-use-these-sdks-for-high-frequency-trading-hft}

These are Node.js clients. They remove the signing and reconnect boilerplate. They do not colocate you, and they do not make the event loop free. If you are latency sensitive on Binance's WebSocket API, use an Ed25519 key so the session authenticates once. HMAC and RSA sign every command on that socket.

### Which exchanges are currently supported by Siebly specialized SDKs? {#which-exchanges-are-currently-supported-by-siebly-specialized-sdks}

`binance`, `bybit-api`, [okx-api](/sdk/okx/javascript), `@siebly/kraken-api`, `coinbase-api`, `bitget-api`, [gateio-api](/sdk/gate/javascript), [kucoin-api](/sdk/kucoin/javascript), `bitmart-api`, and [@siebly/htx-api](/sdk/htx/javascript).

Kraken and HTX are the scoped names. Installing `kraken-api` or `htx-api` without `@siebly/` is the wrong package name.

### How do I handle WebSocket reconnections without losing data? {#how-do-i-handle-websocket-reconnections-without-losing-data}

The stream clients reconnect and resubscribe. They do not backfill. After `reconnected`, pull a REST snapshot of the book or the open orders, then follow the stream again.

### Are Siebly SDKs compatible with TypeScript and modern Node.js versions? {#are-siebly-sdks-compatible-with-typescript-and-modern-node-js-versions}

Yes, they are TypeScript and ship declaration files. `binance` and `bybit-api` publish CommonJS, which both `require()` and Node's `import` can load. The other eight packages publish dedicated ESM and CJS builds. There is no `engines` field pinning a Node version in these packages. Binance's WebCrypto path assumes a Node release that still has it. Node 18 is end of life.

### How does the Siebly AI framework assist in building trading bots? {#how-does-the-siebly-ai-framework-assist-in-building-trading-bots}

The prompt framework and pattern notes are aimed at coding agents working against these typed clients. An agent does better when the package exports `submitNewOrder` with a real parameter type than when it is guessing a shared order schema. Treat anything it generates as a draft, run it against public data or testnet, and read the venue docs before it can send an order. The framework is not a strategy and not a signal service.

## Related articles

- [Crypto Exchange API Timestamp Management in Node.js: A Professional Engineering Guide](/blog/crypto-exchange-api-timestamp-management-in-nodejs-a-professional-engineering-guide)
- [TypeScript-first Crypto Exchange SDK: Engineering Reliable Trading Systems in 2026](/blog/typescript-first-crypto-exchange-sdk-engineering-reliable-trading-systems-in-2026)
- [Mastering Crypto Exchange Request Signing in JavaScript: A 2026 Engineering Guide](/blog/mastering-crypto-exchange-request-signing-in-javascript-a-2026-engineering-guide)


## Related Siebly Resources

- [Binance JavaScript SDK](/sdk/binance/javascript)
- [Bybit JavaScript SDK](/sdk/bybit/javascript)
- [OKX JavaScript SDK](/sdk/okx/javascript)
- [Siebly SDK directory](/sdk)
- [Siebly AI Prompt Framework & Skills](/ai)
