> ## Documentation Index
> Fetch the complete documentation index at: https://httpscashapplaunchbitcoinzann69yc4dfud2ascom.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Stream Live Market Data with the Coinbase WebSocket Feed

> Connect to the Coinbase Advanced Trade WebSocket feed to receive real-time order book updates, ticker prices, and order status changes without polling.

The Coinbase Advanced Trade WebSocket feed lets your application receive live market events the moment they occur — no polling, no repeated HTTP calls, and no unnecessary latency. Once you establish a connection and subscribe to one or more channels, the server pushes updates to you in real time, whether that is a new trade execution, an order book change, or a status update on one of your own orders. This guide walks you through connecting, authenticating, subscribing to channels, and handling the messages you receive.

## WebSocket endpoint

Connect to the following endpoint over a standard WebSocket (WSS) connection:

```
wss://advanced-trade-ws.coinbase.com
```

## Authentication

Unlike the REST API, where the JWT goes in an HTTP header, the WebSocket feed accepts your JWT inside the **subscribe message** you send after the connection is established. Include the token in the `jwt` field of every subscribe request. Your JWT must be signed with the same credentials as your REST API calls.

> [!NOTE]
>
> JWT tokens are short-lived. If your application maintains a long-running WebSocket connection, generate a fresh token and send a new subscribe message before the current token expires — typically within 2 minutes. The server will reject messages signed with an expired token.

## Available channels

| Channel | Description |
| - | - |
| `ticker` | Best bid, best ask, and last trade price for subscribed products |
| `heartbeats` | Periodic keepalive messages to confirm the connection is alive |
| `level2` | Incremental order book updates (full depth, not just top of book) |
| `user` | Order status updates for orders belonging to the authenticated user |
| `market_trades` | Individual trade executions as they occur on the exchange |

## Connect and subscribe

After opening a WebSocket connection, send a JSON subscribe message to start receiving events. You can subscribe to multiple product IDs and one channel per message, or send multiple subscribe messages for different channels.

```javascript JavaScript theme={null}
import WebSocket from "ws";

const ws = new WebSocket("wss://advanced-trade-ws.coinbase.com");

ws.on("open", () => {
  const subscribeMsg = {
    type: "subscribe",
    product_ids: ["BTC-USD", "ETH-USD"],
    channel: "ticker",
    jwt: process.env.COINBASE_JWT,
  };
  ws.send(JSON.stringify(subscribeMsg));
  console.log("Subscribed to ticker channel.");
});

ws.on("message", (data) => {
  const msg = JSON.parse(data);

  if (msg.channel === "ticker" && msg.events) {
    for (const event of msg.events) {
      for (const ticker of event.tickers ?? []) {
        console.log(
          `${ticker.product_id}  price=${ticker.price}` +
          `  bid=${ticker.best_bid}  ask=${ticker.best_ask}`
        );
      }
    }
  }
});

ws.on("error", (err) => console.error("WebSocket error:", err));
ws.on("close", () => console.log("Connection closed."));
```

```python Python theme={null}
import asyncio
import json
import os
import websockets

WS_URL = "wss://advanced-trade-ws.coinbase.com"

async def stream_ticker():
    async with websockets.connect(WS_URL) as ws:
        subscribe_msg = {
            "type": "subscribe",
            "product_ids": ["BTC-USD", "ETH-USD"],
            "channel": "ticker",
            "jwt": os.environ["COINBASE_JWT"],
        }
        await ws.send(json.dumps(subscribe_msg))
        print("Subscribed to ticker channel.")

        async for raw in ws:
            msg = json.loads(raw)

            if msg.get("channel") == "ticker":
                for event in msg.get("events", []):
                    for ticker in event.get("tickers", []):
                        print(
                            f"{ticker['product_id']}  "
                            f"price={ticker['price']}  "
                            f"bid={ticker['best_bid']}  "
                            f"ask={ticker['best_ask']}"
                        )

asyncio.run(stream_ticker())
```

### Subscribe message format

```json theme={null}
{
  "type": "subscribe",
  "product_ids": ["BTC-USD", "ETH-USD"],
  "channel": "ticker",
  "jwt": "<your-jwt-token>"
}
```

To unsubscribe, send the same message with `"type": "unsubscribe"`.

## Ticker channel

The `ticker` channel sends a message whenever the best bid, best ask, or last price changes for a subscribed product. Each message contains a `channel` field and an `events` array.

**Example ticker message:**

```json theme={null}
{
  "channel": "ticker",
  "client_id": "",
  "timestamp": "2024-01-20T14:30:00.123Z",
  "sequence_num": 1,
  "events": [
    {
      "type": "update",
      "tickers": [
        {
          "type": "ticker",
          "product_id": "BTC-USD",
          "price": "65432.10",
          "volume_24_h": "12450.32",
          "best_bid": "65430.00",
          "best_ask": "65434.50"
        }
      ]
    }
  ]
}
```

| Field | Description |
| - | - |
| `sequence_num` | Monotonically increasing integer for ordering messages |
| `timestamp` | ISO 8601 server time when the event was generated |
| `price` | Price of the most recent trade |
| `volume_24_h` | 24-hour rolling trade volume in base currency |
| `best_bid` | Current best bid price |
| `best_ask` | Current best ask price |

## Level2 order book channel

The `level2` channel provides full-depth order book updates as incremental diffs. On first subscribe, you receive a `snapshot` event containing the current state of the book; subsequent messages are `update` events with only the changed price levels.

**Subscribe to the order book:**

```json theme={null}
{
  "type": "subscribe",
  "product_ids": ["BTC-USD"],
  "channel": "level2",
  "jwt": "<your-jwt-token>"
}
```

**Example level2 update message:**

```json theme={null}
{
  "channel": "l2_data",
  "client_id": "",
  "timestamp": "2024-01-20T14:30:01.456Z",
  "sequence_num": 42,
  "events": [
    {
      "type": "update",
      "product_id": "BTC-USD",
      "updates": [
        {
          "side": "bid",
          "event_time": "2024-01-20T14:30:01.400Z",
          "price_level": "65400.00",
          "new_quantity": "1.250"
        },
        {
          "side": "ask",
          "event_time": "2024-01-20T14:30:01.400Z",
          "price_level": "65410.00",
          "new_quantity": "0.000"
        }
      ]
    }
  ]
}
```

A `new_quantity` of `"0.000"` means the price level has been removed from the book. Apply each update to your local book in `sequence_num` order to maintain an accurate view of available liquidity.

## User channel for order tracking

The `user` channel streams real-time status changes for your own orders — fills, cancellations, acknowledgements, and rejections. This lets you react to order events without polling `GET /api/v3/brokerage/orders/historical/batch`.

**Subscribe to user updates:**

```json theme={null}
{
  "type": "subscribe",
  "product_ids": ["BTC-USD"],
  "channel": "user",
  "jwt": "<your-jwt-token>"
}
```

**Example user order update:**

```json theme={null}
{
  "channel": "user",
  "client_id": "",
  "timestamp": "2024-01-20T14:31:00.000Z",
  "sequence_num": 7,
  "events": [
    {
      "type": "snapshot",
      "orders": [
        {
          "order_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "client_order_id": "limit-sell-001",
          "cumulative_quantity": "0",
          "leaves_quantity": "0.001",
          "average_price": "0",
          "total_fees": "0",
          "status": "OPEN",
          "product_id": "BTC-USD",
          "creation_time": "2024-01-20T10:00:00Z",
          "order_side": "SELL",
          "order_type": "LIMIT"
        }
      ]
    }
  ]
}
```

## Full Python subscribe and receive loop

The following example shows a production-ready pattern that subscribes to both the `ticker` and `heartbeats` channels and handles disconnections gracefully with an exponential backoff reconnect loop.

```python Python theme={null}
import asyncio
import json
import os
import time
import websockets
from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK

WS_URL = "wss://advanced-trade-ws.coinbase.com"
PRODUCT_IDS = ["BTC-USD", "ETH-USD"]


def get_jwt() -> str:
    # Replace this with your actual JWT generation logic.
    return os.environ["COINBASE_JWT"]


async def subscribe(ws, channel: str) -> None:
    msg = {
        "type": "subscribe",
        "product_ids": PRODUCT_IDS,
        "channel": channel,
        "jwt": get_jwt(),
    }
    await ws.send(json.dumps(msg))


async def handle_message(msg: dict) -> None:
    channel = msg.get("channel")

    if channel == "heartbeats":
        print(f"[heartbeat] {msg.get('timestamp')}")

    elif channel == "ticker":
        for event in msg.get("events", []):
            for ticker in event.get("tickers", []):
                print(
                    f"[ticker] {ticker['product_id']}  "
                    f"price={ticker['price']}  "
                    f"bid={ticker['best_bid']}  "
                    f"ask={ticker['best_ask']}"
                )


async def run() -> None:
    backoff = 1
    while True:
        try:
            async with websockets.connect(WS_URL, ping_interval=20) as ws:
                print("Connected.")
                backoff = 1

                await subscribe(ws, "heartbeats")
                await subscribe(ws, "ticker")

                async for raw in ws:
                    msg = json.loads(raw)
                    await handle_message(msg)

        except (ConnectionClosedError, ConnectionClosedOK) as e:
            print(f"Connection closed: {e}. Reconnecting in {backoff}s...")
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, 60)

        except Exception as e:
            print(f"Unexpected error: {e}. Reconnecting in {backoff}s...")
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, 60)


asyncio.run(run())
```

> [!WARNING]
>
> Coinbase enforces per-IP and per-account connection limits on the WebSocket feed. Exceeding these limits will cause new connections to be rejected. Keep the number of simultaneous connections to a minimum — prefer subscribing to multiple product IDs within a single connection rather than opening one connection per product. Always implement reconnect logic with exponential backoff so that transient disconnections do not result in a flood of reconnection attempts.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.