Blog
AIWebSocketsTrading systemsTypeScriptNode.js

Reusable Code Examples for Crypto API Integration in Node.js and TypeScript

Discover modular, reusable code examples crypto API developers need to build secure Node.js and TypeScript integrations with REST and awaitable WebSockets.

Siebly.io11 min readMarkdown

Overview

Signing every exchange request yourself, and keeping a raw socket alive, is a good way to stall a trading codebase. HMAC details, reconnects, and payload shapes drift, and the maintenance piles up in code that should have been about orders and state.

Exchange documentation is still the source of truth for each venue. The Siebly SDKs are the Node.js and TypeScript layer in front of that: they sign private REST calls, keep the socket up, and, on the venues that offer a WebSocket API, let you await the reply. Rate limits stay in your application. None of these clients slow or queue traffic for you.

Key Takeaways

  • Private REST calls are signed inside the client. The algorithm is not the same on every venue.
  • Public market streams and private account streams are separate connections. Order placement over a WebSocket returns a promise only where the exchange has a WebSocket API and the SDK wraps it.
  • These SDKs do not throttle. Binance records the weight headers it last saw. Bybit can parse rate-limit headers if you ask. You still decide when to pause.
  • Package names and constructor fields differ. binance uses api_key. bybit-api uses key and secret. OKX, Bitget, and KuCoin also need a passphrase.

Core Challenges of Reusable Crypto Exchange API Integration

Each venue signs requests differently. One wants a query string. Another wants an ISO timestamp, the HTTP method, and the path, then Base64. Copying that logic per exchange turns into a pile of one-off clients.

The Maintenance Cost of Direct REST Integrations

binance signs the query string with a millisecond timestamp. With an HMAC secret that signature is HMAC SHA256. The same client also accepts RSA and Ed25519 keys. If you pass a private key as the secret, it switches mode on its own. Ed25519 is the one that can log in once on the WebSocket API. HMAC and RSA sign each WebSocket command.

okx-api builds timestamp + method + path + body, signs it with HMAC SHA256, and Base64-encodes the result. You also pass apiPass. That passphrase is not optional on private calls.

A clock that is off by one second does not automatically fail every venue. Binance defaults recvWindow to 5000 ms. OKX rejects a timestamp that is outside its server window, which is wider than one second. Kraken spot private calls use a nonce and HMAC SHA512, not a millisecond receive window. Sync the host clock, and treat timestamp errors as a clock problem before you widen any window.

WebSocket Lifecycle Complexities in Real-Time Systems

Public trades are a stream. Placing an order and waiting for the ack is a request with a matching response. The socket clients reconnect, ping, and resubscribe. That part is shared.

Awaitable order calls are not shared. WebsocketAPIClient exists in binance, bybit-api, okx-api, bitget-api (V3 / unified account), gateio-api, kucoin-api, @siebly/kraken-api, and @siebly/htx-api.

coinbase-api and bitmart-api do not offer that. Coinbase's sendWSAPIRequest is a stub and returns nothing. BitMart is subscribe and listen. If you need a promise around an order on those two, use REST.

Modular REST Patterns: Public Market Data and Authenticated Requests

Public calls need no keys. Private calls need the fields that venue actually uses. TypeScript checks the call while you compile. It does not inspect the payload again at runtime, so a bad value can still leave the process if it type-checks.

Structuring Clean Public Market Data Collectors

Binance spot market data goes through MainClient. Keys are optional:

Imported example

TypeScript
import { MainClient } from "binance";

const client = new MainClient();

const book = await client.getOrderBook({ symbol: "BTCUSDT", limit: 10 });
const candles = await client.getKlines({
  symbol: "BTCUSDT",
  interval: "1m",
  limit: 10,
});

Bybit public data uses RestClientV5 with no arguments:

Imported example

TypeScript
import { RestClientV5 } from "bybit-api";

const client = new RestClientV5();
const tickers = await client.getTickers({ category: "linear" });

Kraken spot public data uses the scoped package name:

Imported example

TypeScript
import { SpotClient } from "@siebly/kraken-api";

const client = new SpotClient();
const ticker = await client.getTicker({ pair: "XBTUSD" });

The same idea shows up elsewhere, with different class names: RestClient in okx-api and gateio-api, RestClientV2 or RestClientV3 in bitget-api, SpotClient in kucoin-api, CBAdvancedTradeClient in coinbase-api, RestClient plus FuturesClientV2 in bitmart-api. A short Bybit walkthrough is on the Siebly Bybit tutorial.

Standardizing Authenticated Request Handlers

There is no single { apiKey, apiSecret } constructor. These are the fields the clients actually read:

  • binance: api_key and api_secret. testnet: true or demoTrading: true. Demo trading uses live market data with simulated fills. Testnet is a separate market.
  • bybit-api: key and secret, not apiKey. HMAC or RSA. testnet and demoTrading are separate flags.
  • okx-api: apiKey, apiSecret, and apiPass. Set market to EEA or US when you are not on global. demoTrading: true sends x-simulated-trading.
  • bitget-api: apiKey, apiSecret, and apiPass. demoTrading switches the demo host. New work should use RestClientV3 after the account is on the unified trading account. RestClientV2 is the older API.
  • kucoin-api: apiKey, apiSecret, and apiPassphrase. The passphrase is not the account password.
  • gateio-api: apiKey and apiSecret. Signatures are HMAC SHA512. useTestnet: true on the WebSocket client.
  • @siebly/kraken-api: apiKey and a base64 apiSecret. Signatures are HMAC SHA512.
  • coinbase-api: apiKey is the CDP key name and apiSecret is the EC or Ed25519 private key. Advanced Trade signs a JWT (ES256 or Ed25519). It does not use HMAC SHA256. You can also pass the downloaded key as cdpApiKey.
  • bitmart-api: apiKey, apiSecret, and apiMemo.

Binance private spot:

Imported example

TypeScript
import { MainClient } from "binance";

const client = new MainClient({
  api_key: process.env.BINANCE_API_KEY,
  api_secret: process.env.BINANCE_API_SECRET,
});

const balances = await client.getBalances();

Bybit private V5. The field names are key and secret:

Imported example

TypeScript
import { RestClientV5 } from "bybit-api";

const client = new RestClientV5({
  key: process.env.API_KEY,
  secret: process.env.API_SECRET,
  // testnet: true,
});

const positions = await client.getPositionInfo({
  category: "linear",
  symbol: "BTCUSDT",
});

OKX private calls need the passphrase:

Imported example

TypeScript
import { RestClient } from "okx-api";

const client = new RestClient({
  apiKey: process.env.API_KEY,
  apiSecret: process.env.API_SECRET,
  apiPass: process.env.API_PASSPHRASE,
  // demoTrading: true,
});

const balances = await client.getBalance();

Coinbase Advanced Trade. The secret is a private key, and the client builds a JWT:

Imported example

TypeScript
import { CBAdvancedTradeClient } from "coinbase-api";

const client = new CBAdvancedTradeClient({
  apiKey: process.env.COINBASE_API_KEY_NAME,
  apiSecret: process.env.COINBASE_API_SECRET,
});

const accounts = await client.getAccounts();

Keep keys in the environment or a vault. Turn withdrawals off on any key a bot uses. Secure coding practices still apply after the SDK signs the request for you. The Siebly SDK directory lists the package for each venue.

Demo and sandbox flags are not universal:

  • Binance: testnet or demoTrading.
  • Bybit: testnet or demoTrading. As of the note in the Bybit WebSocket API example (January 2025), demo trading can consume events and does not support the WebSocket API.
  • OKX and Bitget: demoTrading: true.
  • Gate.io WebSocket client: useTestnet: true.
  • BitMart: demoTrading: true only on V2 futures (FuturesClientV2 and the futures socket). Spot stays on production.
  • Kraken: testnet: true only on derivatives. Spot has no testnet in this SDK.
  • Coinbase: useSandbox: true works for Exchange and International. Advanced Trade, the App API, and Prime resolve to NoSandboxAvailable.
  • Do not expect a sandbox by flipping a flag.

Real-Time WebSocket Architecture: Event Streams and Awaitable Workflows

REST polling spends weight and adds a round trip. A socket gives you the next trade when the venue sends it. Keep public market data and private account events on the connections the client already splits for you.

Ingesting Public Streams with Resilient Event Handlers

You subscribe through the client. You do not hand-build the JSON frame. Binance spot trades look like this:

Imported example

TypeScript
import { isWsFormattedTrade, WebsocketClient } from "binance";

const wsClient = new WebsocketClient({ beautify: true });

wsClient.on("formattedMessage", (data) => {
  if (isWsFormattedTrade(data)) {
console.log(data);
  }
});

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

wsClient.subscribeSpotTrades("BTCUSDT");

BitMart's public socket takes a topic string and a market key. No API keys:

Imported example

TypeScript
import { WebsocketClient } from "bitmart-api";

const wsClient = new WebsocketClient();

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

wsClient.subscribe("spot/ticker:BTC_USDT", "spot");

The client pings and reconnects. Your handler should assume a message can arrive twice after a reconnect, and it should not block the process on heavy work.

Executing Actions via Awaitable WebSocket Calls

Where WebsocketAPIClient exists, a call returns a promise. Bybit:

Imported example

TypeScript
import { WebsocketAPIClient } from "bybit-api";

const wsClient = new WebsocketAPIClient({
  key: process.env.API_KEY,
  secret: process.env.API_SECRET,
  // testnet: true,
  // demoTrading does not support the WebSocket API
});

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

Binance uses the same class, with snake_case keys. Pass an Ed25519 private key as api_secret if you want a single login instead of a signature on every command:

Imported example

TypeScript
import { WebsocketAPIClient } from "binance";

const wsClient = new WebsocketAPIClient({
  api_key: process.env.API_KEY,
  api_secret: process.env.API_SECRET,
  beautify: true,
});

const book = await wsClient.getSpotOrderBook({ symbol: "BTCUSDT" });

OKX needs the passphrase inside accounts:

Imported example

TypeScript
import { WebsocketAPIClient } from "okx-api";

const wsClient = new WebsocketAPIClient({
  accounts: [
{
apiKey: process.env.API_KEY || "",
apiSecret: process.env.API_SECRET || "",
apiPass: process.env.API_PASSPHRASE || "",
},
  ],
});

const order = await wsClient.submitNewOrder({
  instId: "BTC-USDT",
  tdMode: "cash",
  side: "buy",
  ordType: "limit",
  px: "10000",
  sz: "0.01",
});

Bitget's promise client is the V3 unified-account API, and it needs apiPass:

Imported example

TypeScript
import { WebsocketAPIClient } from "bitget-api";

const wsClient = new WebsocketAPIClient({
  apiKey: process.env.API_KEY,
  apiSecret: process.env.API_SECRET,
  apiPass: process.env.API_PASS,
  // demoTrading: true,
});

const order = await wsClient.submitNewOrder("spot", {
  orderType: "limit",
  price: "10000",
  qty: "0.001",
  side: "buy",
  symbol: "BTCUSDT",
  timeInForce: "gtc",
});

Gate.io signs with HMAC SHA512 and still returns a promise from WebsocketAPIClient:

Imported example

TypeScript
import { WebsocketAPIClient } from "gateio-api";

const wsClient = new WebsocketAPIClient({
  apiKey: process.env.API_KEY,
  apiSecret: process.env.API_SECRET,
});

const order = await wsClient.submitNewSpotOrder({
  text: "t-my-custom-id",
  currency_pair: "BTC_USDT",
  type: "limit",
  account: "spot",
  side: "buy",
  amount: "0.001",
  price: "10000",
});

KuCoin's WebSocket API client takes apiPassphrase:

Imported example

TypeScript
import { WebsocketAPIClient } from "kucoin-api";

const wsClient = new WebsocketAPIClient({
  apiKey: process.env.API_KEY,
  apiSecret: process.env.API_SECRET,
  apiPassphrase: process.env.API_PASSPHRASE,
});

const order = await wsClient.submitNewSpotOrder({
  side: "buy",
  symbol: "BTC-USDT",
  type: "limit",
  price: "10000",
  size: "0.0001",
});

Kraken spot orders over the socket:

Imported example

TypeScript
import { WebsocketAPIClient } from "@siebly/kraken-api";

const wsClient = new WebsocketAPIClient({
  apiKey: process.env.API_SPOT_KEY,
  apiSecret: process.env.API_SPOT_SECRET,
});

const order = await wsClient.submitSpotOrder({
  order_type: "limit",
  side: "buy",
  limit_price: 10000,
  order_qty: 0.001,
  symbol: "BTC/USD",
});

coinbase-api and bitmart-api stop at subscriptions. Place those orders with the REST client.

Operational Best Practices: Rate Limits, State, and Security Boundaries

The SDK signs and reconnects. It does not decide which request is worth sending. Queues, token buckets, and backoff belong in your process.

Implementing Application-Level Throttling and Backoff

Limits are not one shared shape.

Binance spot REST uses request weight per IP. The usual ceiling is 6,000 weight per minute. The response header is X-MBX-USED-WEIGHT-1M. A breach returns HTTP 429. If you keep calling after that, Binance bans the IP with HTTP 418. The binance client stores the last weight headers it saw and does not pause:

Imported example

TypeScript
const states = client.getRateLimitStates();
// states['x-mbx-used-weight-1m']

SAPI and futures use other pools. Read exchangeInfo if you need the live numbers.

Bybit does not throttle either. Set parseAPIRateLimits: true on RestClientV5 if you want parsed rate-limit headers on the response. You still back off yourself.

Coinbase is not "10 requests per second per IP" for every Advanced Trade call. That 10 per second per IP figure is the public bucket. Private Advanced Trade routes have a separate per-key budget, and Coinbase changes it. Coinbase Exchange (CBExchangeClient) is a different API: public REST there is 10 requests per second per IP, with a short burst above that, and private calls are counted per profile. Confirm the current page before you hardcode a limiter.

Kraken has two counters, and the 2.34 figure is only one of them. Spot order add and cancel use a per-pair trading counter. On the Intermediate tier (Kraken's support pages also call this Standard verification) that counter decays by 2.34 per second and rejects once it passes 125. Private REST calls such as balances use another counter. On the Standard tier that one maxes at 20 and decays by 0.5 per second. Trading endpoints do not add to the REST counter.

A practical throttle:

  • Read the venue's own headers or error before you invent a global cap.
  • Keep market-data calls off the same bucket as order calls when the venue splits them.
  • On HTTP 429, wait. Honor Retry-After when it is present. Add jitter so every worker does not retry together.
  • Rebuild balances and positions from a private REST snapshot on a timer. The socket can drop a frame during a reconnect.

Defensive Key Management and Testnet Workflows

Create keys that can read or trade, and leave withdrawals off. Do not commit secrets. Use the demo or testnet flag that the venue actually implements, from the list above. Kraken spot, KuCoin, and Coinbase Advanced Trade will not give you a sandbox just because a flag exists on another client. State handling notes are on the Siebly exchange state guides.

Implementing Production-Ready Solutions with Siebly.io SDKs

The packages share a habit: a typed client, signed private calls, and a socket that reconnects. They do not share one constructor or one signature scheme. bybit-api, binance, okx-api, and @siebly/kraken-api are four different clients that happen to live next to each other.

What stays similar, and what does not

coinbase-api, bitget-api, gateio-api, kucoin-api, and bitmart-api follow the same split between REST and WebSocket clients. Method names follow the venue. BitMart's spot order call is submitSpotOrderV2, not a generic createOrder. Gate.io's open orders call is getSpotOrders with currency_pair and status.

@siebly/htx-api is in the same family: SpotClient, FuturesClient, and WebsocketAPIClient.

What you can rely on:

  • You do not write the HMAC, RSA, Ed25519, or JWT step yourself.
  • TypeScript types follow the request and response shapes published for that package.
  • The socket client reconnects and resubscribes.

What you cannot assume:

  • One set of constructor field names.
  • HMAC SHA256 on every package. Kraken and Gate.io use SHA512. Coinbase Advanced Trade uses a JWT.
  • A promise-based WebSocket order API on Coinbase or BitMart.
  • A working testnet flag on every client.

Using the types with a coding agent

The clients ship TypeScript types for methods and payloads. An agent that reads those types is less likely to invent a parameter. Types disappear after compile, so they are not a runtime guard. Patterns for this are listed under Siebly AI patterns. The package index is at Siebly.io.

Standardize Your Crypto Integration Architecture

Pick the client for the venue, pass the fields that client documents, and keep the limiter in your own code. Binance, Bybit, OKX, Kraken, and Coinbase do not share a signature or a constructor. They do share the part that usually rots first: signing, reconnects, and typed responses.

Use a demo or testnet flag where that venue has one. Leave withdrawals off. Read weight and rate-limit headers yourself. The SDK will not stop you from getting a 429.

Siebly.io JavaScript and TypeScript SDKs are the packages behind the samples above.

Frequently Asked Questions

Do Siebly SDKs automatically manage exchange rate limits and request throttling?

No. They do not queue or slow requests. binance records the last X-MBX-USED-WEIGHT headers and exposes them through getRateLimitStates(). bybit-api parses rate-limit headers only when parseAPIRateLimits is true. Neither client will pause a call that would breach the limit. That pause belongs in your token bucket or leaky bucket.

Can I use Siebly SDKs to execute requests over WebSockets rather than REST?

On binance, bybit-api, okx-api, bitget-api (V3), gateio-api, kucoin-api, @siebly/kraken-api, and @siebly/htx-api, yes. WebsocketAPIClient returns a promise. coinbase-api and bitmart-api only subscribe. Use their REST clients when you need a response you can await. Bybit demo trading, as of the January 2025 note in the SDK examples, does not support the WebSocket API.

How do specialized SDKs differ from generic multi-exchange wrapper libraries?

Each package follows one venue's parameters, including the odd ones a shared wrapper tends to drop. You do not get one normalized order object. You do get the passphrase, memo, category, and signature that venue requires, and a WebSocket API client where that venue has one.

What security permissions should I configure on exchange API keys for automated software?

Enable read or trade. Leave withdrawals off. Load secrets from the environment or a vault. Bind the key to an IP when the exchange allows it.

Is it possible to develop and test integration pipelines without using live capital?

On some venues, yes. Binance has testnet and demo trading. Bybit has testnet and demo trading, with the WebSocket API gap on demo noted above. OKX and Bitget have demoTrading. Gate.io has useTestnet. BitMart demo mode is V2 futures only. Kraken testnet is derivatives only. Coinbase sandbox is Exchange and International, not Advanced Trade. KuCoin has no usable testnet host in this SDK. Public market data is available on every client without a key.

Which JavaScript and Node.js exchange packages are currently maintained by Siebly.io JavaScript SDKs?

binance, bybit-api, okx-api, @siebly/kraken-api, coinbase-api, bitget-api, gateio-api, kucoin-api, bitmart-api, and @siebly/htx-api. Each one has TypeScript types and a reconnecting WebSocket client. Awaitable WebSocket orders depend on the venue, as covered above.

Related articles

Continue from here

Related Siebly resources

All articles

Subscribe on Substack

Complete the Substack form below to join our newsletter. Substack handles all subscriber data directly.