<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Siebly]]></title><description><![CDATA[Open-source JavaScript and TypeScript SDKs for crypto exchange APIs. https://siebly.io]]></description><link>https://siebly.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 11:39:18 GMT</lastBuildDate><atom:link href="https://siebly.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Bitget books50 goes incremental on 1 December: keeping a local order book in Node.js]]></title><description><![CDATA[What changes on the 50-level depth channel, and a small TypeScript-friendly handler that works before and after the switch]]></description><link>https://siebly.hashnode.dev/bitget-books50-goes-incremental-on-1-december-keeping-a-local-order-book-in-node-js</link><guid isPermaLink="true">https://siebly.hashnode.dev/bitget-books50-goes-incremental-on-1-december-keeping-a-local-order-book-in-node-js</guid><category><![CDATA[Bitget]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[websockets]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Siebly]]></dc:creator><pubDate>Tue, 10 Nov 2026 11:36:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6af1b1b24234b6e89a5b92f3/66814f50-b389-419d-b700-87cbd76e8be0.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Bitget is changing how its 50-level order book channel works. From 1 December 2026 (already live on demo trading), <code>books50</code> on the V3 Unified Account WebSocket sends one full <code>snapshot</code> after you subscribe and then only incremental <code>update</code> messages, the same pattern as the full-depth <code>books</code> channel. The <code>pseq</code> field also starts carrying a value on <code>books50</code>, so you can spot dropped messages. The details are on Bitget's <a href="https://www.bitget.com/docs/uta/update-preview">update preview page</a>.</p>
<p>If your code treats every <code>books50</code> message as the whole book, it will quietly break: after the first message you'd replace 50 levels with the two or three that changed. Here's how to handle it with <code>bitget-api</code>, the MIT-licensed Bitget SDK for Node.js, JavaScript and TypeScript. It's from the same maintainers as the binance, bybit-api and okx-api JS SDKs, with over 4.5 million npm downloads across 10+ SDKs.</p>
<h2>What changes</h2>
<table>
<thead>
<tr>
<th></th>
<th>Before 1 Dec</th>
<th>From 1 Dec</th>
</tr>
</thead>
<tbody><tr>
<td>First message</td>
<td><code>snapshot</code> with 50 levels</td>
<td><code>snapshot</code> with 50 levels, <code>pseq</code> = 0</td>
</tr>
<tr>
<td>Later messages</td>
<td><code>snapshot</code> with 50 levels, every push</td>
<td><code>update</code> with only the changed levels</td>
</tr>
<tr>
<td>Removed level</td>
<td>Missing from the next snapshot</td>
<td>Sent with size <code>0</code></td>
</tr>
<tr>
<td><code>pseq</code></td>
<td>Not set</td>
<td><code>seq</code> of the previous message</td>
</tr>
</tbody></table>
<p><code>books1</code> and <code>books5</code> don't change: every message on those is still a full snapshot.</p>
<h2>Subscribe</h2>
<pre><code class="language-bash">npm install bitget-api
</code></pre>
<pre><code class="language-javascript">import { WebsocketClientV3, WS_KEY_MAP } from 'bitget-api';

const ws = new WebsocketClientV3({});

ws.subscribe(
  [{ topic: 'books50', payload: { instType: 'spot', symbol: 'BTCUSDT' } }],
  WS_KEY_MAP.v3Public,
);
</code></pre>
<p>Each <code>update</code> event from the SDK has <code>action</code> (<code>snapshot</code> or <code>update</code>), <code>arg</code> (the topic, symbol and instType you subscribed to), <code>data</code> and <code>wsKey</code>. <code>data[0]</code> holds <code>a</code> (asks), <code>b</code> (bids), <code>seq</code> and <code>pseq</code>. The client handles heartbeats, reconnects and resubscribes for you.</p>
<h2>Keep a local book</h2>
<p>The handler below resets the book on a <code>snapshot</code> and applies changes on an <code>update</code>. Because it branches on <code>action</code> rather than on the date, it works today (when every message is a snapshot) and after the switch.</p>
<pre><code class="language-javascript">const books = new Map(); // key: instType:symbol

function applyLevels(side, levels) {
  for (const [price, size] of levels) {
    if (Number(size) === 0) side.delete(price);
    else side.set(price, size);
  }
}

ws.on('update', (event) =&gt; {
  if (event.arg?.topic !== 'books50') return;

  const key = `${event.arg.instType}:${event.arg.symbol}`;
  const msg = event.data[0];

  if (event.action === 'snapshot') {
    const book = { asks: new Map(), bids: new Map(), seq: msg.seq };
    applyLevels(book.asks, msg.a);
    applyLevels(book.bids, msg.b);
    books.set(key, book);
    return;
  }

  const book = books.get(key);
  if (!book) return; // wait for a snapshot

  if (String(msg.pseq) !== String(book.seq)) {
    console.warn(`Gap on ${key}: expected pseq ${book.seq}, got ${msg.pseq}. Resyncing.`);
    books.delete(key);
    ws.unsubscribe([{ topic: 'books50', payload: event.arg }], WS_KEY_MAP.v3Public);
    ws.subscribe([{ topic: 'books50', payload: event.arg }], WS_KEY_MAP.v3Public);
    return;
  }

  applyLevels(book.asks, msg.a);
  applyLevels(book.bids, msg.b);
  book.seq = msg.seq;
});

function topOfBook(key) {
  const book = books.get(key);
  if (!book) return undefined;
  const bestAsk = [...book.asks.keys()].sort((x, y) =&gt; Number(x) - Number(y))[0];
  const bestBid = [...book.bids.keys()].sort((x, y) =&gt; Number(y) - Number(x))[0];
  return { bestBid, bestAsk };
}

setInterval(() =&gt; console.log(topOfBook('spot:BTCUSDT')), 1000);
</code></pre>
<p>A few notes:</p>
<ul>
<li>Prices stay as strings, which avoids float rounding in your map keys. Convert only when you compare or calculate.</li>
<li>Bitget says sequence numbers can reset during releases or restarts, and you'll usually see <code>pseq=0</code> when that happens. Resubscribing gives you a fresh snapshot, which is the simplest safe recovery.</li>
<li>If you only need the best bid and ask, <code>books1</code> stays a full snapshot on every push and needs none of this.</li>
<li>Want a REST starting point too? <code>RestClientV3.getOrderBook()</code> returns the current book in one call.</li>
</ul>
<h2>Test it before 1 December</h2>
<p>Bitget's demo environment already sends incremental <code>books50</code>, so you can check your handler there before production switches. On production today you'll still see a <code>snapshot</code> on every message, which the handler above also treats correctly.</p>
<h2>More</h2>
<ul>
<li><a href="https://siebly.io/sdk/bitget/javascript">Bitget JavaScript SDK docs</a></li>
<li><a href="https://siebly.io/sdk/bitget/javascript/tutorial">Bitget API JavaScript tutorial</a> for REST, private streams and WebSocket API orders</li>
<li><a href="https://github.com/sieblyio/bitget-api">bitget-api on GitHub</a></li>
</ul>
<h2>More docs</h2>
<p>API reference and usage guides also live on the <a href="https://sieblyio.github.io/docs/">Siebly docs portal</a> (Hyperliquid, Bybit and Binance pages so far).</p>
]]></content:encoded></item><item><title><![CDATA[KuCoin API JavaScript tutorial for Node.js: REST and WebSocket]]></title><description><![CDATA[Public and private REST calls, Classic WebSocket streams and a safe test order with kucoin-api]]></description><link>https://siebly.hashnode.dev/kucoin-api-javascript-tutorial-for-node-js-rest-and-websocket</link><guid isPermaLink="true">https://siebly.hashnode.dev/kucoin-api-javascript-tutorial-for-node-js-rest-and-websocket</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[kucoin]]></category><category><![CDATA[websockets]]></category><dc:creator><![CDATA[Siebly]]></dc:creator><pubDate>Mon, 09 Nov 2026 17:22:03 GMT</pubDate><content:encoded><![CDATA[<p>Private KuCoin REST requests need an API key, a timestamp, an HMAC signature, an HMAC-signed passphrase and the key version. Classic WebSocket connections need a connection token first, and each subscription has to reach the right product connection. This tutorial walks through <code>kucoin-api</code>, an MIT-licensed Node.js, JavaScript and TypeScript SDK for KuCoin, from install to a test order that never reaches the matching engine.</p>
<p>The SDK comes from the same maintainers as the binance, bybit-api and okx-api JS SDKs, with over 4.5 million npm downloads across 10+ SDKs. The overview lives in the <a href="https://siebly.io/sdk/kucoin/javascript">KuCoin JavaScript SDK docs</a>, and the full walkthrough (Futures test orders, the WebSocket API, reconnect recovery, regional hosts and proxies) is the <a href="https://siebly.io/sdk/kucoin/javascript/tutorial">KuCoin API JavaScript tutorial for Node.js</a>.</p>
<h2>What you get</h2>
<table>
<thead>
<tr>
<th>Client</th>
<th>Use it for</th>
</tr>
</thead>
<tbody><tr>
<td><code>SpotClient</code></td>
<td>Classic Spot, Margin, funding, transfer, Earn and Convert REST calls</td>
</tr>
<tr>
<td><code>FuturesClient</code></td>
<td>Classic Futures market, account, order, position and funding REST calls</td>
</tr>
<tr>
<td><code>WebsocketClient</code></td>
<td>Classic and Pro public and private WebSocket subscriptions</td>
</tr>
<tr>
<td><code>WebsocketAPIClient</code></td>
<td>Promise-based Spot, Margin and Futures order commands over WebSocket</td>
</tr>
</tbody></table>
<p>Start with the Classic clients unless you specifically need the newer Unified Account or Pro APIs.</p>
<h2>Install and API keys</h2>
<pre><code class="language-bash">npm install kucoin-api
</code></pre>
<p>Public market data needs no credentials. Private calls, private streams and order commands need three values: <code>KUCOIN_API_KEY</code>, <code>KUCOIN_API_SECRET</code> and <code>KUCOIN_API_PASSPHRASE</code>. The passphrase is the one you chose when creating the key, not your account password.</p>
<p>Use a General-only key while you build account views, and add Spot or Futures permission only when you start testing orders. Nothing here needs Withdrawal permission. Keep keys server-side and out of logs.</p>
<p>A few naming details save time later: Spot symbols look like <code>BTC-USDT</code>, Futures symbols like <code>XBTUSDTM</code>, and a Futures <code>size</code> is a number of contracts rather than an amount of BTC. Successful REST responses come back as <code>{ code: '200000', data }</code>.</p>
<h2>1. Public REST</h2>
<pre><code class="language-javascript">import { SpotClient } from 'kucoin-api';

const client = new SpotClient();

async function main() {
  const time = await client.getServerTime();
  console.log('Server time:', time.data);

  const ticker = await client.getTicker({ symbol: 'BTC-USDT' });
  console.log('BTC-USDT ticker:', ticker.data);

  const book = await client.getOrderBookLevel20({ symbol: 'BTC-USDT' });
  console.log('Best bid:', book.data.bids[0], 'best ask:', book.data.asks[0]);

  const candles = await client.getKlines({ symbol: 'BTC-USDT', type: '1hour' });
  console.log('Latest candle:', candles.data[0]);
}

main().catch(console.error);
</code></pre>
<p>Prices and sizes are usually strings. Keep them that way when sending values back to KuCoin, and convert deliberately when you calculate.</p>
<h2>2. Private REST</h2>
<p>The same client takes credentials and signs every request.</p>
<pre><code class="language-javascript">import { SpotClient } from 'kucoin-api';

const client = new SpotClient({
  apiKey: process.env.KUCOIN_API_KEY,
  apiSecret: process.env.KUCOIN_API_SECRET,
  apiPassphrase: process.env.KUCOIN_API_PASSPHRASE,
});

async function main() {
  const balances = await client.getBalances({ type: 'trade' });
  console.log('Spot trading balances:', balances.data);

  const openOrders = await client.getHFActiveOrdersPaginated({
    symbol: 'BTC-USDT',
    pageNum: 1,
    pageSize: 20,
  });
  console.log('Open orders:', openOrders.data.items);
}

main().catch(console.error);
</code></pre>
<p>The client throws when KuCoin returns a non-success business code. Parsed errors carry the HTTP status, body, headers and request context, with the key, secret and passphrase left out. Methods with <code>HF</code> in the name map to KuCoin's current high-frequency Spot endpoints.</p>
<h2>3. Public WebSocket streams</h2>
<p>Classic subscriptions take a topic and a <code>wsKey</code>. The SDK fetches the connection token, opens the right connection, keeps it alive and resubscribes after a reconnect.</p>
<pre><code class="language-javascript">import { WebsocketClient, WS_KEY_MAP } from 'kucoin-api';

const ws = new WebsocketClient();

ws.on('update', (update) =&gt; console.log(update.wsKey, update.topic, update.data));
ws.on('response', (response) =&gt; console.log('response', response.type));
ws.on('reconnected', ({ wsKey }) =&gt; console.log('reconnected', wsKey));
ws.on('exception', (error) =&gt; console.error('ws error', error));

ws.subscribe('/market/ticker:BTC-USDT', WS_KEY_MAP.spotPublicV1);
ws.subscribe('/contractMarket/tickerV2:XBTUSDTM', WS_KEY_MAP.futuresPublicV1);

process.once('SIGINT', () =&gt; ws.closeAll());
</code></pre>
<p>A <code>welcome</code> response means the connection is ready, and an <code>ack</code> confirms the subscription. Market data arrives through <code>update</code>.</p>
<h2>4. Private streams</h2>
<p>Spot and Futures private topics use separate connections, so keep the <code>wsKey</code> with every event.</p>
<pre><code class="language-javascript">import { WebsocketClient, WS_KEY_MAP } from 'kucoin-api';

const ws = new WebsocketClient({
  apiKey: process.env.KUCOIN_API_KEY,
  apiSecret: process.env.KUCOIN_API_SECRET,
  apiPassphrase: process.env.KUCOIN_API_PASSPHRASE,
});

ws.on('update', (update) =&gt; console.log('private', update.wsKey, update.topic));
ws.on('exception', (error) =&gt; console.error('ws error', error));

ws.subscribe(['/spotMarket/tradeOrdersV2', '/account/balance'], WS_KEY_MAP.spotPrivateV1);
ws.subscribe(
  ['/contractMarket/tradeOrders:XBTUSDTM', '/contractAccount/wallet'],
  WS_KEY_MAP.futuresPrivateV1,
);
</code></pre>
<h2>5. A Spot test order</h2>
<p>KuCoin's test-order endpoint checks authentication and order fields without touching the matching engine. Read the symbol metadata first so the size meets the minimums.</p>
<pre><code class="language-javascript">import { SpotClient } from 'kucoin-api';

const client = new SpotClient({
  apiKey: process.env.KUCOIN_API_KEY,
  apiSecret: process.env.KUCOIN_API_SECRET,
  apiPassphrase: process.env.KUCOIN_API_PASSPHRASE,
});

async function main() {
  const { data: symbol } = await client.getSymbol({ symbol: 'BTC-USDT' });
  const { data: ticker } = await client.getTicker({ symbol: 'BTC-USDT' });

  const price = Number(ticker.bestBid);
  const step = Number(symbol.baseIncrement);
  const minSize = Math.max(Number(symbol.baseMinSize), Number(symbol.minFunds) / price);
  const decimals = (symbol.baseIncrement.split('.')[1] || '').length;
  const size = (Math.ceil(minSize / step) * step).toFixed(decimals);

  const result = await client.submitHFOrderTest({
    clientOid: client.generateNewOrderID(),
    symbol: 'BTC-USDT',
    side: 'buy',
    type: 'limit',
    price: ticker.bestBid,
    size,
    timeInForce: 'GTC',
    postOnly: true,
  });
  console.log('Spot order test:', result);
}

main().catch(console.error);
</code></pre>
<p>The test endpoint is good for checking credentials, permissions, signing and order fields. It doesn't test matching, fills, cancellation or private order updates; the full tutorial covers those with the WebSocket API.</p>
<h2>Where to go next</h2>
<ul>
<li><a href="https://siebly.io/sdk/kucoin/javascript">KuCoin JavaScript SDK docs</a> for the client overview.</li>
<li><a href="https://siebly.io/sdk/kucoin/javascript/tutorial">KuCoin API JavaScript tutorial</a> for Futures test orders, the WebSocket API, reconnect recovery, regional hosts and proxies.</li>
<li><a href="https://github.com/sieblyio/kucoin-api">kucoin-api on GitHub</a> for examples and the endpoint map.</li>
</ul>
<h2>More docs</h2>
<p>API reference and usage guides also live on the <a href="https://sieblyio.github.io/docs/">Siebly docs portal</a> (Hyperliquid, Bybit and Binance pages so far).</p>
]]></content:encoded></item><item><title><![CDATA[Hyperliquid JavaScript SDK quickstart for Node.js and TypeScript]]></title><description><![CDATA[Install @siebly/hyperliquid-api, read public market data, set up a signed client on testnet and subscribe to Hyperliquid WebSocket streams.]]></description><link>https://siebly.hashnode.dev/hyperliquid-javascript-sdk-quickstart-for-node-js-and-typescript</link><guid isPermaLink="true">https://siebly.hashnode.dev/hyperliquid-javascript-sdk-quickstart-for-node-js-and-typescript</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[hyperliquid]]></category><dc:creator><![CDATA[Siebly]]></dc:creator><pubDate>Mon, 09 Nov 2026 12:11:27 GMT</pubDate><content:encoded><![CDATA[<p>Hyperliquid exposes two main HTTP endpoints (Info and Exchange) plus a WebSocket feed. You can call them directly, but signing, nonces, asset IDs and reconnect handling add up quickly. This quickstart walks through <code>@siebly/hyperliquid-api</code>, an MIT-licensed SDK for Node.js, JavaScript and TypeScript, from install to a live WebSocket subscription.</p>
<p>Everything below uses classes and methods exported by the package. The full reference lives in the <a href="https://siebly.io/sdk/hyperliquid/javascript">Hyperliquid JavaScript SDK docs</a>.</p>
<h2>What you get</h2>
<ul>
<li>One <code>RestClient</code> for public market data, account information, orders, transfers and more.</li>
<li>A <code>WebsocketClient</code> with heartbeats, automatic reconnect and resubscribe.</li>
<li>A <code>WebsocketAPIClient</code> that mirrors the REST methods over Hyperliquid's WebSocket POST protocol.</li>
<li>TypeScript declarations for requests and responses, with dual ESM and CommonJS builds.</li>
<li>Testnet support through a single <code>testnet: true</code> option.</li>
</ul>
<h2>Install</h2>
<p>The package supports Node.js 22 or newer.</p>
<pre><code class="language-bash">npm install @siebly/hyperliquid-api
</code></pre>
<p>TypeScript is optional, but the type declarations make the request shapes much easier to discover in your editor.</p>
<h2>Your first public REST call</h2>
<p>Public Info methods need no credentials. Start by checking the exchange status, then pull mid prices and an order book:</p>
<pre><code class="language-ts">import { RestClient } from '@siebly/hyperliquid-api';

const client = new RestClient();

async function main() {
  const status = await client.getExchangeStatus();
  console.log('Exchange status:', status);

  // AllMidsResponse is Record&lt;string, string&gt;: coin to mid price
  const mids = await client.getAllMids();
  console.log('Coins with a mid price:', Object.keys(mids).length);
  console.log('BTC mid:', mids['BTC'] ?? 'not listed');

  const book = await client.getOrderBook({
    coin: 'BTC',
    nSigFigs: 5,
  });

  if (book) {
    console.log('Best bid level:', book.levels[0][0]);
  }
}

main().catch((error) =&gt; {
  console.error('Request failed:', error);
});
</code></pre>
<p>The SDK uses readable <code>get...</code> names rather than Hyperliquid's internal request type names, so <code>getAllMids()</code> maps to the <code>allMids</code> Info request and <code>getOrderBook()</code> to <code>l2Book</code>.</p>
<p>You can also read any public account by address, without a key:</p>
<pre><code class="language-ts">const user = '0x1234567890123456789012345678901234567890';

const perpSummary = await client.getPerpAccountSummary({ user });
const spotBalances = await client.getSpotBalances({ user });
</code></pre>
<h2>Authenticated client setup</h2>
<p>Signed methods take a private key, and that key belongs on a trusted backend only. Read it from an environment variable rather than hard-coding it:</p>
<pre><code class="language-ts">import { RestClient } from '@siebly/hyperliquid-api';

const privateKey = process.env.HYPERLIQUID_PRIVATE_KEY;

if (!privateKey) {
  throw new Error('Set HYPERLIQUID_PRIVATE_KEY before starting.');
}

const client = new RestClient({
  privateKey,
  testnet: true,
});

const balances = await client.getBalances();
console.log('Account:', balances.user);
console.log('Spot:', balances.spot.balances);
console.log('Perpetuals:', balances.perp.assetPositions);

const openOrders = await client.getOpenOrders();
console.log('Open orders:', openOrders.length);
</code></pre>
<p>A few notes:</p>
<ul>
<li><strong>Use testnet first.</strong> <code>testnet: true</code> points the client at Hyperliquid's testnet API, so you can exercise signing and order flows without touching a real account.</li>
<li><strong>Keep keys server-side.</strong> Never put a private key in frontend code, browser storage or a public environment variable. Public REST calls and public subscriptions are browser-safe; signed calls are not.</li>
<li><strong>Custom signers.</strong> If you would rather not hand the SDK a raw key, the constructor also accepts a <code>signer</code> with a portable <code>signTypedData</code> implementation.</li>
</ul>
<p>Once that works, the client exposes helpers for common order workflows. This places a post-only limit order on testnet:</p>
<pre><code class="language-ts">const order = await client.submitLimitOrder({
  symbol: 'BTC',
  side: 'buy',
  size: '0.001',
  price: '50000',
  timeInForce: 'GTC',
});

if (order.status === 'ok') {
  console.log(order.response.data);
}
</code></pre>
<p>Exchange methods return an <code>ExchangeResponse</code> wrapper, so check <code>status === 'ok'</code> before reading <code>response.data</code>. For the full order surface, including <code>submitOrder()</code> with raw Hyperliquid order fields and <code>submitMarketOrder()</code>, see the <a href="https://siebly.io/sdk/hyperliquid/javascript/tutorial">Hyperliquid API JavaScript tutorial</a>.</p>
<h2>Subscribing to WebSocket streams</h2>
<p><code>WebsocketClient</code> opens connections when they are first needed, deduplicates matching subscriptions, sends heartbeats, and reconnects then resubscribes after a drop. You register listeners once and subscribe to topics separately:</p>
<pre><code class="language-ts">import { WebsocketClient } from '@siebly/hyperliquid-api';

const ws = new WebsocketClient({ testnet: true });

ws.on('message', (event) =&gt; {
  if (event.channel === 'l2Book') {
    console.log(event.wsKey, event.data.coin, event.data.levels);
  } else if (event.channel === 'trades') {
    console.log(event.wsKey, event.data);
  }
});

ws.on('response', (event) =&gt; {
  console.log('Acknowledgement:', event.wsKey, event.channel, event.data);
});

ws.on('reconnected', ({ wsKey }) =&gt; {
  console.log('Reconnected:', wsKey);
});

ws.on('exception', ({ wsKey, event, data }) =&gt; {
  console.error('WebSocket exception:', wsKey, event ?? data);
});

const book = {
  type: 'l2Book',
  coin: 'BTC',
  nSigFigs: 5,
} as const;

const trades = { type: 'trades', coin: 'ETH' } as const;

await ws.subscribe([book, trades]);

// Later, when the feeds are no longer needed:
// await ws.unsubscribe([book, trades]);
// ws.closeAll();
</code></pre>
<p><code>subscribe()</code> resolves once the socket is open and the request has been sent. The server's acknowledgement arrives on the <code>response</code> event, and data arrives on <code>message</code> with the upstream envelope intact plus a <code>wsKey</code>.</p>
<p>Coin identifiers such as <code>BTC</code>, <code>@107</code> or <code>PURR/USDC</code> pass straight through. If you want to resolve a readable alias first, there is a helper:</p>
<pre><code class="language-ts">const coin = await ws.nameToCoin('HYPE/USDC');
await ws.subscribe({ type: 'trades', coin });
</code></pre>
<p>Reconnect timing, ping interval and pong deadline are all configurable through the constructor options if the defaults do not suit your deployment.</p>
<h3>Request and response over WebSockets</h3>
<p>If you want the REST-style methods but over a persistent socket, <code>WebsocketAPIClient</code> provides the same Info and Exchange methods as <code>RestClient</code>:</p>
<pre><code class="language-ts">import { WebsocketAPIClient } from '@siebly/hyperliquid-api';

const client = new WebsocketAPIClient({ testnet: true });
const mids = await client.getAllMids();
console.log(mids);
client.close();
</code></pre>
<h2>Debugging tips</h2>
<ul>
<li>Pass a custom logger as the second argument to <code>WebsocketClient</code> to see pings, subscriptions and messages at trace level.</li>
<li><code>RestClient</code> accepts Axios-compatible network options as its second argument, which lets you route requests through a local debugging proxy. Avoid logging signed <code>/exchange</code> bodies.</li>
<li>If Hyperliquid ships a new operation before the SDK has a typed wrapper, <code>submitRawInfoRequest()</code> keeps it usable in the meantime.</li>
</ul>
<h2>Next steps</h2>
<ul>
<li>Read the <a href="https://siebly.io/sdk/hyperliquid/javascript">Hyperliquid JavaScript SDK docs</a> for the overview and feature list.</li>
<li>Work through the <a href="https://siebly.io/sdk/hyperliquid/javascript/tutorial">Hyperliquid API JavaScript tutorial for Node.js</a>, which covers perpetual and Spot orders, account streams, the WebSocket API, testnet and proxies in more depth.</li>
<li>Follow the <a href="https://github.com/sieblyio/hyperliquid-api/blob/main/docs/HYPERLIQUID_SDK_QUICKSTART_GUIDE.md">Hyperliquid SDK quickstart guide on GitHub</a> for complete end-to-end workflows.</li>
<li>Check <a href="https://siebly.io/releases?sdk=hyperliquid-api">Hyperliquid SDK release notes</a> for recent changes.</li>
<li>Browse the source and examples in the <a href="https://github.com/sieblyio/hyperliquid-api">sieblyio/hyperliquid-api repository</a>, or install from <a href="https://www.npmjs.com/package/@siebly/hyperliquid-api">npm</a>.</li>
</ul>
<p>Questions or bug reports are welcome on the GitHub issues tab.</p>
]]></content:encoded></item><item><title><![CDATA[OKX WebSocket port 8443 is retired: fixing Node.js connections]]></title><description><![CDATA[OKX stopped accepting WebSocket connections on port 8443 on 31 October 2026 (Colo follows on 16 November). If your Node.js app still connects to…]]></description><link>https://siebly.hashnode.dev/okx-websocket-port-8443-is-retired-fixing-node-js-connections</link><guid isPermaLink="true">https://siebly.hashnode.dev/okx-websocket-port-8443-is-retired-fixing-node-js-connections</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[okx]]></category><category><![CDATA[websockets]]></category><dc:creator><![CDATA[Siebly]]></dc:creator><pubDate>Mon, 09 Nov 2026 11:46:57 GMT</pubDate><content:encoded><![CDATA[<p>OKX stopped accepting WebSocket connections on port 8443 on 31 October 2026 (Colo follows on 16 November). If your Node.js app still connects to <code>wss://ws.okx.com:8443/...</code>, the connection now fails. REST calls keep working, so a service can look healthy while its price feed has stopped.</p>
<h2>The fix</h2>
<p>Only the port changes. The host and path stay the same, and <code>wss://</code> uses 443 by default.</p>
<pre><code class="language-text">wss://ws.okx.com:8443/ws/v5/public     -&gt;  wss://ws.okx.com/ws/v5/public
wss://ws.okx.com:8443/ws/v5/private    -&gt;  wss://ws.okx.com/ws/v5/private
wss://ws.okx.com:8443/ws/v5/business   -&gt;  wss://ws.okx.com/ws/v5/business
wss://wspap.okx.com:8443/ws/v5/public  -&gt;  wss://wspap.okx.com/ws/v5/public   (demo)
</code></pre>
<p>The same applies to the EEA and US hosts (<code>wseea.okx.com</code>, <code>wsus.okx.com</code> and their demo versions).</p>
<p>Look for leftovers in config, env vars and deploy scripts:</p>
<pre><code class="language-sh">grep -rn ":8443" .
</code></pre>
<h2>Check it's really fixed</h2>
<p>Getting connected isn't enough. Confirm that subscriptions are acknowledged and that updates keep arriving:</p>
<pre><code class="language-js">import WebSocket from 'ws';

const ws = new WebSocket('wss://ws.okx.com/ws/v5/public');

ws.on('open', () =&gt; {
  ws.send(JSON.stringify({
    op: 'subscribe',
    args: [{ channel: 'tickers', instId: 'BTC-USDT' }],
  }));
  setInterval(() =&gt; ws.send('ping'), 25_000);
});

ws.on('message', (raw) =&gt; {
  const text = raw.toString();
  if (text === 'pong') return;
  console.log(JSON.parse(text));
});
</code></pre>
<h2>If you use an SDK</h2>
<p>Check which version you have installed and whether it hard-codes a port. The <a href="https://siebly.io/sdk/okx/javascript">OKX JavaScript SDK</a> (<code>okx-api</code>) connects on the default port and has no <code>:8443</code> in its endpoints. It also defaults REST to OKX's newer <code>https://openapi.okx.com</code> domain. The <a href="https://siebly.io/sdk/okx/javascript/tutorial">OKX JavaScript tutorial</a> covers reconnects and private streams.</p>
<p>Source: <a href="https://www.okx.com/en-us/help/okx-websocket-port-8443-discontinuation-announcement">OKX port 8443 announcement</a>.</p>
]]></content:encoded></item></channel></rss>