---
title: "Reusable Code Examples for Crypto API Integration in Node.js"
description: "Discover modular, reusable code examples crypto API developers need to build secure Node.js and TypeScript integrations with REST and awaitable WebSockets."
canonical: "https://siebly.io/blog/reusable-code-examples-for-crypto-api-integration-in-nodejs-and-typescript"
---

# 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.

## 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 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 {#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](/sdk/binance/javascript) uses `api_key`. [bybit-api](/sdk/bybit/javascript) uses `key` and `secret`. OKX, Bitget, and KuCoin also need a passphrase.



## Core Challenges of Reusable Crypto Exchange API Integration {#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 {#the-maintenance-cost-of-direct-rest-integrations}

[binance](/sdk/binance/javascript) 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](/sdk/okx/javascript) 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](/sdk/kraken/javascript) 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 {#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](/sdk/bybit/javascript), `okx-api`, [bitget-api](/sdk/bitget/javascript) (V3 / unified account), [gateio-api](/sdk/gate/javascript), [kucoin-api](/sdk/kucoin/javascript), `@siebly/kraken-api`, and [@siebly/htx-api](/sdk/htx/javascript).

[coinbase-api](/sdk/coinbase/javascript) 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 {#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 {#structuring-clean-public-market-data-collectors}

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

```ts title="Imported example"
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:

```ts title="Imported example"
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:

```ts title="Imported example"
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](/sdk/okx/javascript) and [gateio-api](/sdk/gate/javascript), `RestClientV2` or `RestClientV3` in [bitget-api](/sdk/bitget/javascript), `SpotClient` in [kucoin-api](/sdk/kucoin/javascript), `CBAdvancedTradeClient` in [coinbase-api](/sdk/coinbase/javascript), `RestClient` plus `FuturesClientV2` in `bitmart-api`. A short Bybit walkthrough is on the [Siebly Bybit tutorial](/sdk/bybit/javascript/tutorial).

### Standardizing Authenticated Request Handlers {#standardizing-authenticated-request-handlers}

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

- [binance](/sdk/binance/javascript): `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](/sdk/bybit/javascript): `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](/sdk/kraken/javascript): `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:

```ts title="Imported example"
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`:

```ts title="Imported example"
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:

```ts title="Imported example"
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:

```ts title="Imported example"
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](https://www.ibm.com/topics/secure-coding) still apply after the SDK signs the request for you. The [Siebly SDK directory](/sdk) 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 {#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 {#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:

```ts title="Imported example"
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:

```ts title="Imported example"
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 {#executing-actions-via-awaitable-websocket-calls}

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

```ts title="Imported example"
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:

```ts title="Imported example"
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`:

```ts title="Imported example"
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`:

```ts title="Imported example"
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`:

```ts title="Imported example"
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`:

```ts title="Imported example"
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:

```ts title="Imported example"
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](/sdk/coinbase/javascript) and `bitmart-api` stop at subscriptions. Place those orders with the REST client.

## Operational Best Practices: Rate Limits, State, and Security Boundaries {#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 {#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](/sdk/binance/javascript) client stores the last weight headers it saw and does not pause:

```ts title="Imported example"
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 {#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](/ai/exchange-state).

## Implementing Production-Ready Solutions with Siebly.io SDKs {#implementing-production-ready-solutions-with-sieblyio-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](/sdk/bybit/javascript), [binance](/sdk/binance/javascript), [okx-api](/sdk/okx/javascript), and [@siebly/kraken-api](/sdk/kraken/javascript) are four different clients that happen to live next to each other.

### What stays similar, and what does not {#what-stays-similar-and-what-does-not}

[coinbase-api](/sdk/coinbase/javascript), [bitget-api](/sdk/bitget/javascript), [gateio-api](/sdk/gate/javascript), [kucoin-api](/sdk/kucoin/javascript), and [bitmart-api](/sdk/bitmart/javascript) 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](/sdk/htx/javascript) 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 {#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](/ai/patterns). The package index is at [Siebly.io](/).

## Standardize Your Crypto Integration Architecture {#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 {#frequently-asked-questions}

### Do Siebly SDKs automatically manage exchange rate limits and request throttling? {#do-siebly-sdks-automatically-manage-exchange-rate-limits-and-request-throttling}

No. They do not queue or slow requests. [binance](/sdk/binance/javascript) records the last `X-MBX-USED-WEIGHT` headers and exposes them through `getRateLimitStates()`. [bybit-api](/sdk/bybit/javascript) 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? {#can-i-use-siebly-sdks-to-execute-requests-over-websockets-rather-than-rest}

On `binance`, `bybit-api`, [okx-api](/sdk/okx/javascript), [bitget-api](/sdk/bitget/javascript) (V3), [gateio-api](/sdk/gate/javascript), [kucoin-api](/sdk/kucoin/javascript), [@siebly/kraken-api](/sdk/kraken/javascript), and [@siebly/htx-api](/sdk/htx/javascript), yes. `WebsocketAPIClient` returns a promise. [coinbase-api](/sdk/coinbase/javascript) 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? {#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? {#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? {#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? {#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

- [Scalable Crypto API Integration in Node.js: A Practical Engineering Guide](/blog/scalable-crypto-api-integration-in-nodejs-a-practical-engineering-guide)
- [HTX - new addition to Siebly SDKs](/blog/htx-new-addition-to-siebly-sdks)
- [Engineering Guide: Testnet Trading Bot Development in Node.js (2026)](/blog/engineering-guide-testnet-trading-bot-development-in-nodejs-2026)


## 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)
