Blog
AIWebSocketsTrading systemsTypeScriptNode.js

Coinbase Pro to Advanced Trade Migration Guide

Learn how to migrate from Coinbase Pro API to Advanced Trade Node.js. This guide covers V3 auth, WebSockets, and idempotency using the coinbase-api SDK.

Siebly.io11 min readMarkdown

Overview

The Coinbase Pro API shut down on June 6, 2024. If you still have a Node.js stack talking to Pro private endpoints, those calls fail. Retail trading moved to Advanced Trade V3. Institutional Pro access moved to Coinbase Exchange. Those are different APIs, different auth, different clients. This guide is for the retail path: migrate from coinbase pro api to advanced trade nodejs.

The coinbase-api SDK is the implementation layer we use for that work. It signs every private REST call with a short-lived JWT, attaches a JWT to private WebSocket subscribe messages, and gives you typed request shapes. It does not rate-limit or throttle for you. That part stays in your code.

Key Takeaways

  • Pro used API key + secret + passphrase and HMAC headers. Advanced Trade uses Cloud API keys: a key name and a private key (ECDSA or Ed25519). Auth is a JWT in an Authorization: Bearer header, not HMAC-SHA256.
  • HMAC with CB-ACCESS-KEY, CB-ACCESS-SIGN, CB-ACCESS-TIMESTAMP, and CB-ACCESS-PASSPHRASE is still how Coinbase Exchange and International work. Prime is the same idea with different header names: X-CB-ACCESS-KEY, X-CB-ACCESS-SIGNATURE, X-CB-ACCESS-TIMESTAMP, X-CB-ACCESS-PASSPHRASE. Do not copy any of that signing code onto Advanced Trade.
  • Orders go over REST. Coinbase Advanced Trade has no request/response WebSocket API for placing orders. WebsocketClient.sendWSAPIRequest() in this package returns immediately and does nothing. Other Siebly SDKs wrap those exchanges' real WebSocket APIs: binance, bybit-api, okx-api, @siebly/kraken-api, @siebly/htx-api, bitget-api, gateio-api, kucoin-api. bitmart-api does not, because BitMart does not ship that API. Coinbase does not have one either.
  • Public market data lives on wss://advanced-trade-ws.coinbase.com. Private user data lives on wss://advanced-trade-ws-user.coinbase.com. The SDK reconnects and resubscribes on drop.
  • client_order_id is required by Coinbase. The SDK generates one if you omit it, and prefixes custom values with cbnode.

Coinbase Pro to Advanced Trade: Context & Deprecation

Coinbase Pro accounts were disabled on December 1, 2023. The Pro API itself was deprecated on June 6, 2024. Retail bots need the Advanced Trade V3 REST and WebSocket APIs. Institutional users should look at Coinbase Exchange instead (CBExchangeClient in the same package), which still uses passphrase HMAC auth.

You cannot reuse Pro keys. You cannot reuse Pro paths like /products.

Why Legacy Pro Integrations Fail

Pro signed every private call with HMAC-SHA256 over timestamp + method + path + body, then sent CB-ACCESS-* headers plus a passphrase. Advanced Trade dropped the passphrase. Cloud API keys are a name (often organizations/.../apiKeys/...) and a private key. The SDK detects ECDSA PEM keys (ES256) and Ed25519 keys (EdDSA) automatically.

If you send Pro credentials or HMAC headers to V3, Coinbase rejects the request. The signature format is a JWT, not a base64 HMAC. The header is Authorization: Bearer.

The 2026 API Landscape for Coinbase

Advanced Trade is the retail gateway for spot and futures. Coinbase currently lists 550+ spot pairs. Cloud API keys support IP allowlists (recommended, not required) and scoped permissions (view, trade, transfer).

REST traffic is commonly cited at 10,000 requests per hour per API key under Coinbase App policy. WebSocket connections are limited to 8 per second per IP. Unauthenticated WebSocket messages are a separate 8 per second per IP cap. The SDK does not enforce those limits. You own the queue and backoff.

The coinbase-api package covers Advanced Trade, App, Exchange, International, Prime, and Commerce. For this migration you want CBAdvancedTradeClient and WebsocketClient.

Mapping Authentication: JWT, not HMAC

This is the breaking change people get wrong when they migrate from coinbase pro api to advanced trade nodejs. Pro: key, secret, passphrase. Advanced Trade: Cloud API key name plus private key. No passphrase.

JWT lifetime in the SDK defaults to 120 seconds (jwtExpiresSeconds). Coinbase expects a fresh JWT per request. The SDK builds one on every private REST call and on every authenticated WebSocket subscribe.

What the SDK actually signs

For REST it builds a JWT with iss: 'cdp', sub and kid set to the key name, nbf/exp from the local clock, and a uri claim of METHOD host/path. Algorithm is ES256 or EdDSA depending on the key. That token goes out as:

Imported example

Text
Authorization: Bearer

For private WebSocket subscribe messages it builds a similar JWT (no uri claim) and puts it on the subscribe payload as jwt.

Clock drift still matters because of nbf and exp. Keep NTP honest. The client also exposes fetchLatencySummary() if you want to compare local time against Coinbase server time.

Do not implement HMAC-SHA256 and attach CB-ACCESS-SIGN. That is Exchange and International. Prime uses X-CB-ACCESS-SIGNATURE instead of CB-ACCESS-SIGN. Mixing any of that onto Advanced Trade is the fastest way to a wall of 401s.

If you actually need Coinbase Exchange, that is a different client and a passphrase:

Imported example

JavaScript
const { CBExchangeClient } = require("coinbase-api");
// import { CBExchangeClient } from 'coinbase-api';

const exchangeClient = new CBExchangeClient({
  apiKey: process.env.EXCHANGE_API_KEY,
  apiSecret: process.env.EXCHANGE_API_SECRET,
  // Passphrase you set when creating the Exchange key, not your account password.
  apiPassphrase: process.env.EXCHANGE_API_PASSPHRASE,
  // useSandbox: true,
});

Installing and creating a client

Imported example

Shell
npm install coinbase-api

Imported example

JavaScript
const { CBAdvancedTradeClient } = require("coinbase-api");
// import { CBAdvancedTradeClient } from 'coinbase-api';

// ECDSA (PEM) or Ed25519 (base64). The client detects both.
const client = new CBAdvancedTradeClient({
  apiKey: process.env.API_KEY_NAME,
  apiSecret: process.env.API_PRIVATE_KEY,
  // Or pass the downloaded CDP JSON:
  // cdpApiKey: { name: '...', privateKey: '...' },
});

async function getAccounts() {
  try {
const accounts = await client.getAccounts({ limit: 10 });
console.log(accounts);

if (accounts.accounts.length > 0) {
const accountDetails = await client.getAccount({
account_id: accounts.accounts[0].uuid,
});
console.log(accountDetails);
}
  } catch (e) {
console.error("Get accounts error: ", e);
  }
}

getAccounts();

Public REST needs no keys. Same client, empty options:

Imported example

JavaScript
const { CBAdvancedTradeClient } = require("coinbase-api");

const client = new CBAdvancedTradeClient({});

async function publicCalls() {
  const serverTime = await client.getServerTime();
  console.log(serverTime);

  const productBook = await client.getPublicProductBook({
product_id: "BTC-USD",
limit: 10,
  });
  console.log(productBook);

  const { products } = await client.getPublicProducts({
product_type: "SPOT",
  });
  const btc = products.find((p) => p.product_id === "BTC-USD");
  console.log(btc?.base_increment, btc?.quote_increment, btc?.base_min_size);
}

publicCalls().catch(console.error);

Keep keys in env or a secret manager. Scope them to view and trade. Leave withdrawal/transfer off unless you actually need it. IP allowlisting is extra defense, not a Coinbase requirement.

The SDK signs for you. It does not throttle. Stay under the hourly REST cap yourself.

WebSocket Migration: Market Data and User Streams

The Pro feed URL is gone. Advanced Trade splits public and private traffic:

  • Market data: SDK wsKey advTradeMarketData, URL wss://advanced-trade-ws.coinbase.com
  • User data: SDK wsKey advTradeUserData, URL wss://advanced-trade-ws-user.coinbase.com

Ticker, candles, level2, status, ticker_batch, and similar public channels go to advTradeMarketData. On advTradeUserData you subscribe to user (orders and fills) and futures_balance_summary (futures balances). Subscribe on the user key and the SDK injects the JWT. You do not send a separate auth handshake.

Coinbase drops the socket if it does not get a subscribe within 5 seconds of connect. The SDK sends stored topics as soon as the socket is up.

Coinbase has no sandbox for Advanced Trade WebSockets. useSandbox only works for Exchange and International. Prime, App, Advanced Trade, and Commerce have no sandbox URL. If you flip useSandbox on those, the SDK has nowhere to send the request.

Public market data

Imported example

JavaScript
const { WebsocketClient } = require("coinbase-api");

const client = new WebsocketClient();

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

client.on("update", (data) => {
  console.info("data", JSON.stringify(data));
});

client.on("response", (data) => {
  console.info("response", JSON.stringify(data));
});

client.on("reconnect", (data) => console.log("reconnect", data));
client.on("reconnected", (data) => console.log("reconnected", data));
client.on("close", (data) => console.error("close", data));
client.on("exception", (data) => console.error("exception", data));

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

client.subscribe(
  [
{ topic: "heartbeats" },
{ topic: "candles", payload: { product_ids: ["ETH-USD"] } },
{
topic: "market_trades",
payload: { product_ids: ["ETH-USD", "BTC-USD"] },
},
{
topic: "status",
payload: { product_ids: ["ETH-USD", "BTC-USD"] },
},
{
topic: "ticker_batch",
payload: { product_ids: ["ETH-USD", "BTC-USD"] },
},
{ topic: "level2", payload: { product_ids: ["ETH-USD", "BTC-USD"] } },
  ],
  "advTradeMarketData",
);

Ticker payloads changed from Pro. Parse the V3 schema, do not reuse old field names.

Subscribe to heartbeats if you want Coinbase's own keepalive channel. The SDK also pings the socket and reconnects on close/error, then resubscribes stored topics. Listen for reconnect and reconnected if you keep local books.

Stay within 8 unauthenticated messages per second per IP, and 8 new connections per second per IP. Filter in the update handler so you only work on pairs you trade.

Private user stream

Imported example

JavaScript
const { WebsocketClient } = require("coinbase-api");

const client = new WebsocketClient({
  apiKey: process.env.API_KEY_NAME,
  apiSecret: process.env.API_PRIVATE_KEY,
});

client.on("update", (data) => {
  console.info("user event", JSON.stringify(data));
});

client.on("response", (data) => {
  console.info("response", JSON.stringify(data));
});

client.on("exception", (data) => console.error("exception", data));

// Any subscribe on advTradeUserData is signed automatically.
client.subscribe("user", "advTradeUserData");
client.subscribe("futures_balance_summary", "advTradeUserData");

Use user for fills and order updates. Use futures_balance_summary for futures balances. Polling getOrders() for every state change burns your hourly budget.

You cannot place an order over this socket and await an ack. That pattern exists in Siebly SDKs for exchanges that ship a WebSocket API. Coinbase Advanced Trade does not. sendWSAPIRequest in coinbase-api is unimplemented. It returns undefined.

This is what the same idea looks like on Binance, where the exchange actually has a WebSocket API:

Imported example

JavaScript
const { WebsocketAPIClient } = require("binance");

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

async function placeOnSocket() {
  const result = await ws.submitNewSpotOrder({
symbol: "BTCUSDT",
side: "SELL",
type: "LIMIT",
timeInForce: "GTC",
price: "23416.10000000",
quantity: "0.00847000",
  });
  console.log(result);
}

placeOnSocket().catch(console.error);

Bybit is new WebsocketAPIClient({ key, secret }) then submitNewOrder(). OKX takes an accounts array with apiKey, apiSecret, apiPass. Bitget needs apiPass. KuCoin needs apiPassphrase. Kraken, HTX, and Gate take apiKey plus apiSecret. None of that applies here.

Place Coinbase orders with CBAdvancedTradeClient.submitOrder(). Watch the user channel for the result.

Engineering Best Practices for Order Management

Pro order bodies used flat fields (size, price, type). V3 wraps size and price inside order_configuration. The SDK keys are:

  • Market IOC: market_market_ioc
  • Limit GTC: limit_limit_gtc
  • Limit GTD: limit_limit_gtd
  • Limit FOK: limit_limit_fok
  • SOR limit IOC: sor_limit_ioc
  • Stop-limit GTC/GTD: stop_limit_stop_limit_gtc, stop_limit_stop_limit_gtd
  • TWAP: twap_limit_gtd
  • Brackets: trigger_bracket_gtc, trigger_bracket_gtd

client_order_id is required by the API. If you omit it, the SDK fills client.generateNewOrderId() (cbnode plus a 14-character nanoid). If you pass your own id without that prefix, the SDK prepends cbnode and logs a warning. Reuse the same id on retry so Coinbase treats it as one order.

Submit a market and a limit order

Imported example

JavaScript
const { CBAdvancedTradeClient } = require("coinbase-api");

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

async function submitMarketSell() {
  try {
const result = await client.submitOrder({
product_id: "BTC-USDT",
side: "SELL",
client_order_id: client.generateNewOrderId(),
order_configuration: {
market_market_ioc: { base_size: "0.001" },
},
});
console.log(result);
  } catch (e) {
console.error("Send new order error: ", e);
  }
}

async function submitLimitBuy() {
  try {
const result = await client.submitOrder({
product_id: "BTC-USDT",
side: "BUY",
client_order_id: client.generateNewOrderId(),
order_configuration: {
limit_limit_gtc: {
base_size: "0.001",
limit_price: "50000.00",
},
},
});
console.log(result);
  } catch (e) {
console.error("Submit limit order error: ", e);
  }
}

submitMarketSell();
submitLimitBuy();

Statuses are not just PENDING then FILLED. Coinbase uses PENDING, OPEN, FILLED, CANCELLED, EXPIRED, FAILED, QUEUED, CANCEL_QUEUED, EDIT_QUEUED, and UNKNOWN_ORDER_STATUS. Track those off the user channel, then reconcile with getOrder / getOrders when you need a hard snapshot.

Imported example

JavaScript
async function getOrders() {
  try {
const orders = await client.getOrders({ limit: 5 });
console.log(orders);

if (orders.orders.length > 0) {
const details = await client.getOrder({
order_id: orders.orders[0].order_id,
});
console.log(details);
}
  } catch (e) {
console.error("Error: ", e);
  }
}

getOrders();

Increments and rate limits

Fetch product specs before you send size and price. Increments differ per product. Public product data does not need keys:

Imported example

JavaScript
const { products } = await client.getPublicProducts({ product_type: "SPOT" });
const btc = products.find((p) => p.product_id === "BTC-USD");
// btc.base_increment, btc.quote_increment, btc.base_min_size

The authenticated twin is getProducts({ product_type: "SPOT" }) on the same client. Same increment fields.

REST: plan around 10,000 requests per hour per key (Coinbase App policy, the most specific published number), and watch 429s plus rate-limit headers. WebSocket: 8 connections per second per IP, and 8 unauthenticated messages per second per IP. Token bucket or leaky bucket in your process. The SDK will not do this.

Modernizing Your Integration with the Siebly coinbase-api SDK

A hand-rolled V3 client means you own JWT construction, key-type detection, header injection, subscribe signing, reconnect, and type drift every time Coinbase changes a field. coinbase-api already does that for Advanced Trade and the other Coinbase API groups in the same npm package.

It is TypeScript-first. Request and response shapes are typed. Same patterns as the other Siebly exchange SDKs (binance, bybit-api, okx-api, kucoin-api, gateio-api, bitget-api, bitmart-api, @siebly/kraken-api, @siebly/htx-api). Coding agents work better against those types and the bundled llms.txt. See the Siebly AI Prompt Framework and Skills if you generate integration code with an LLM.

What it does not do: rate limits, throttling, or Coinbase WebSocket order entry (because Coinbase does not offer it).

Getting started

Install coinbase-api. Pass the Cloud API key name and private key (or cdpApiKey). Public REST and public sockets need no keys. Private REST and advTradeUserData do.

Docs and more samples: Siebly Coinbase SDK documentation. The GitHub repo also has runnable examples under examples/AdvancedTrade/ (REST orders, accounts, public and private sockets).

Migrate from coinbase pro api to advanced trade nodejs with that client, not with a leftover Pro HMAC wrapper.

Modernizing Your Trading Infrastructure

Pro is gone. Advanced Trade wants JWT Cloud keys, /api/v3/brokerage paths, nested order_configuration, and two WebSocket hosts. client_order_id keeps retries idempotent. The user channel keeps local order state honest.

CBAdvancedTradeClient and WebsocketClient from coinbase-api cover signing, reconnect, and types. You still write the rate limiter. Explore the Siebly Coinbase JavaScript SDK and start from the Advanced Trade examples rather than a Pro leftover.

Frequently Asked Questions

Can I still use my Coinbase Pro API keys for Advanced Trade?

No. Pro keys were key + secret + passphrase. Advanced Trade keys are a Cloud API key name and a private key (ECDSA PEM or Ed25519). Create new credentials on the Coinbase Developer Platform. Scope them as tightly as you can.

If you were on Pro as an institution, you likely want Coinbase Exchange keys and CBExchangeClient, which still uses HMAC and a passphrase (CB-ACCESS-* headers). Prime is HMAC too, but the headers are X-CB-ACCESS-*.

What is the main difference between Coinbase Pro and Advanced Trade APIs?

Auth, paths, and order shape. Pro HMAC headers and /products are gone. V3 uses JWT Bearer auth and /api/v3/brokerage/.... Orders nest size and price under order_configuration. Market data and user data use two different WebSocket URLs.

How do I handle authentication in the Coinbase Advanced Trade V3 API?

Generate a JWT (ES256 or EdDSA) per private REST request and put it in Authorization: Bearer. Private WebSocket subscribe messages carry a jwt field. coinbase-api does both. You pass apiKey + apiSecret (or cdpApiKey) and call methods. Do not build HMAC CB-ACCESS-SIGN headers for Advanced Trade.

Does the Siebly coinbase-api SDK handle rate limiting automatically?

No. That is intentional. Put a leaky bucket or token bucket in your app. Treat 10,000 REST requests per hour per key as the headline cap. WebSocket: 8 connections per second per IP, and 8 unauthenticated messages per second per IP. Watch 429s.

What is the awaitable WebSocket pattern in Siebly SDKs?

On exchanges that expose a WebSocket API, several Siebly SDKs wrap commands in promises (WebsocketAPIClient on binance, bybit-api, okx-api, @siebly/kraken-api, @siebly/htx-api, bitget-api, gateio-api, kucoin-api). You send an order on the socket and await the ack. bitmart-api has no WebsocketAPIClient because BitMart does not offer that API.

Coinbase Advanced Trade does not have that API. coinbase-api does not pretend it does. Submit over REST, subscribe to user on advTradeUserData for live updates.

Bybit, from the SDK examples:

Imported example

JavaScript
const { WebsocketAPIClient } = require("bybit-api");

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

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

main().catch(console.error);

Is there a sandbox environment for testing Advanced Trade integrations?

No. The SDK marks Advanced Trade, App, Prime, and Commerce as NoSandboxAvailable. Exchange and International do have sandboxes. For Advanced Trade, use public product data, tiny live sizes, and keys with no transfer permission.

How do I manage WebSocket reconnection in Node.js for Coinbase?

WebsocketClient reconnects on close/error, emits reconnect then reconnected, and resubscribes. You still need to rebuild local books after a gap. Subscribe to heartbeats on the market-data socket if you want Coinbase's keepalive channel on top of the SDK ping/pong.

Are Siebly SDKs compatible with TypeScript and coding agents?

Yes. They ship TypeScript types for requests and responses. Every Siebly exchange SDK on npm includes llms.txt. The Siebly AI Prompt Framework and Skills is the documented way to point an LLM at these packages.

Disclaimer

*Technical and legal disclaimer: Siebly.io provides software development tools, SDKs, documentation, and educational engineering content for crypto exchange API integrations. This content is for software engineering education only and is not financial, investment, legal, tax, accounting, compliance, or trading advice.

Nothing in this article is a recommendation, invitation, or inducement to buy, sell, hold, trade, long, short, or allocate to any cryptoasset, exchange product, strategy, bot, or automated workflow. Examples, code patterns, simulations, backtests, and architecture diagrams are illustrative only and must not be treated as trading signals, investment recommendations, or evidence of future performance.

Cryptoasset markets are high risk and volatile. If you choose to build or operate exchange-connected software, you are responsible for your own legal, regulatory, tax, security, exchange-account, API-key, and risk-management obligations. Use public data, testnet, demo, dry-run, or paper-trading workflows before any live execution. Keep API keys server-side, use least-privilege permissions, and never enable withdrawals for automation keys unless you fully understand and accept the risks.

Siebly.io is not an exchange, custodian, investment adviser, trading-signal provider, or managed trading service. Official exchange documentation remains the source of truth for exchange-specific rules, API behavior, and terms of use. Use of Siebly.io content and software is also subject to the Siebly.io terms and conditions.*

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.