> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cdp.coinbase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced Trade WebSocket Overview

The WebSocket feed is publicly available and provides real-time market data updates for orders and trades. Two endpoints are supported in production:

* **Market Data** is our traditional feed that provides updates for both orders and trades. Most channels are now available without authentication.
* **User Order Data** provides updates for the orders of the user.

<Info>
  **Market Data** Endpoint: `wss://advanced-trade-ws.coinbase.com`<br />
  **User Order Data** Endpoint: `wss://advanced-trade-ws-user.coinbase.com`
</Info>

<Tip>
  You can subscribe to the Heartbeats Channel, User Channel and Futures Balance Summary Channel with the User Order Data endpoint. If advanced-trade-ws-user is your primary connection, we recommend using advanced-trade-ws as a failover.
</Tip>

## Protocol

The WebSocket feed uses a bidirectional protocol that encodes all messages as JSON objects. All messages have a `type` attribute that can be used to handle the message appropriately.

<Tip>
  New message types can be added at any time. Clients are expected to ignore messages they do not support.
</Tip>

## Sending Messages with CDP Keys

### Subscribing

To begin receiving feed messages, you must send a `subscribe` message to the server indicating which channel and products to receive. This message is mandatory and you are disconnected if no `subscribe` has been received within 5 seconds.

You can subscribe to multiple channels but you must send a unique subscription message for each channel.

<Warning>
  To receive feed messages, you must send a `subscribe` message or you are disconnected in 5 seconds.
</Warning>

```json lines wrap theme={null}
// Request
// Subscribe to ETH-USD and ETH-EUR with the level2 channel
{
  "type": "subscribe",
  "product_ids": ["ETH-USD", "ETH-EUR"],
  "channel": "level2",
  "jwt": "exampleJWT"
}
```

To subscribe to any channel, provide a `channel` name and `jwt`:

* `channel` name as a string. You can only subscribe to one channel per subscription message.

* `jwt` can be generated by running one of the code snippets provided in the [WebSocket Authentication page](/coinbase-app/advanced-trade-apis/websocket/websocket-authentication). Remember that you must generate a different JWT for each websocket message sent, since the JWTs will expire after 2 minutes.

### Unsubscribing

To unsubscribe from a channel/product pair, send an `unsubscribe` message. The structure is the same as `subscribe` message. You can only unsubscribe from one channel per subscription message. You can also unsubscribe from a channel entirely by providing no product IDs.

```json lines wrap theme={null}
// Request
{
  "type": "unsubscribe",
  "product_ids": ["ETH-USD", "ETH-EUR"],
  "channel": "level2",
  "jwt": "exampleJWT"
}
```

You receive a `subscriptions` message as a response to an `unsubscribe` message.

## Websocket Code Samples

Use the code samples to subscribe to one or more Advanced Trade WebSocket channels using CDP API Keys.

<Tabs groupId="programming-language">
  <Tab value="javascript" title="JavaScript">
    ```javascript lines wrap theme={null}
    // JS Example for subscribing to a channel
    /* eslint-disable */
    const WebSocket = require("ws");
    const { sign } = require("jsonwebtoken");
    const crypto = require("crypto");
    const fs = require("fs");

    // Derived from your Coinbase CDP API Key
    //  SIGNING_KEY: the signing key provided as a part of your API key. Also called the "SECRET KEY"
    //  API_KEY: the api key provided as a part of your API key. also called the "API KEY NAME"
    const API_KEY = "organizations/{org_id}/apiKeys/{key_id}";
    const SIGNING_KEY =
      "-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n";

    const algorithm = "ES256";

    if (!SIGNING_KEY.length || !API_KEY.length) {
      throw new Error("missing mandatory environment variable(s)");
    }

    const CHANNEL_NAMES = {
      level2: "level2",
      user: "user",
      tickers: "ticker",
      ticker_batch: "ticker_batch",
      status: "status",
      market_trades: "market_trades",
      candles: "candles",
    };

    // The base URL of the API
    const WS_API_URL = "wss://advanced-trade-ws.coinbase.com";

    function signWithJWT(message, channel, products = []) {
      const jwt = sign(
        {
          iss: "cdp",
          nbf: Math.floor(Date.now() / 1000),
          exp: Math.floor(Date.now() / 1000) + 120,
          sub: API_KEY,
        },
        SIGNING_KEY,
        {
          algorithm,
          header: {
            kid: API_KEY,
            nonce: crypto.randomBytes(16).toString("hex"),
          },
        }
      );

      return { ...message, jwt: jwt };
    }

    const ws = new WebSocket(WS_API_URL);

    function subscribeToProducts(products, channelName, ws) {
      const message = {
        type: "subscribe",
        channel: channelName,
        product_ids: products,
      };
      const subscribeMsg = signWithJWT(message, channelName, products);
      ws.send(JSON.stringify(subscribeMsg));
    }

    function unsubscribeToProducts(products, channelName, ws) {
      const message = {
        type: "unsubscribe",
        channel: channelName,
        product_ids: products,
      };
      const subscribeMsg = signWithJWT(message, channelName, products);
      ws.send(JSON.stringify(subscribeMsg));
    }

    function onMessage(data) {
      const parsedData = JSON.parse(data);
      fs.appendFile("Output1.txt", data, (err) => {
        // In case of a error throw err.
        if (err) throw err;
      });
    }

    const connections = [];
    let sentUnsub = false;
    for (let i = 0; i < 1; i++) {
      const date1 = new Date(new Date().toUTCString());
      const ws = new WebSocket(WS_API_URL);

      ws.on("message", function (data) {
        const date2 = new Date(new Date().toUTCString());
        const diffTime = Math.abs(date2 - date1);
        if (diffTime > 5000 && !sentUnsub) {
          unsubscribeToProducts(["BTC-USD"], CHANNEL_NAMES.level2, ws);
          sentUnsub = true;
        }

        const parsedData = JSON.parse(data);
        fs.appendFile("Output1.txt", data, (err) => {
          // In case of a error throw err.
          if (err) throw err;
        });
      });

      ws.on("open", function () {
        const products = ["BTC-USD"];
        subscribeToProducts(products, CHANNEL_NAMES.level2, ws);
      });

      connections.push(ws);
    }
    ```
  </Tab>

  <Tab value="python" title="Python">
    ```python lines wrap theme={null}
    # Python Example for subscribing to a channel
    import time
    import json
    import jwt
    import hashlib
    import os
    import websocket
    import threading
    from datetime import datetime, timedelta

    # Derived from your Coinbase CDP API Key
    # SIGNING_KEY: the signing key provided as a part of your API key. Also called the "SECRET KEY"
    # API_KEY: the api key provided as a part of your API key. also called the "API KEY NAME"
    API_KEY = "organizations/{org_id}/apiKeys/{key_id}"
    SIGNING_KEY = """-----BEGIN EC PRIVATE KEY-----
    YOUR PRIVATE KEY
    -----END EC PRIVATE KEY-----"""

    ALGORITHM = "ES256"

    if not SIGNING_KEY or not API_KEY:
        raise ValueError("Missing mandatory environment variable(s)")

    CHANNEL_NAMES = {
        "level2": "level2",
        "user": "user",
        "tickers": "ticker",
        "ticker_batch": "ticker_batch",
        "status": "status",
        "market_trades": "market_trades",
        "candles": "candles",
    }

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

    def sign_with_jwt(message, channel, products=[]):
        payload = {
            "iss": "coinbase-cloud",
            "nbf": int(time.time()),
            "exp": int(time.time()) + 120,
            "sub": API_KEY,
        }
        headers = {
            "kid": API_KEY,
            "nonce": hashlib.sha256(os.urandom(16)).hexdigest()
        }
        token = jwt.encode(payload, SIGNING_KEY, algorithm=ALGORITHM, headers=headers)
        message['jwt'] = token
        return message

    def on_message(ws, message):
        data = json.loads(message)
        with open("Output1.txt", "a") as f:
            f.write(json.dumps(data) + "\n")

    def subscribe_to_products(ws, products, channel_name):
        message = {
            "type": "subscribe",
            "channel": channel_name,
            "product_ids": products
        }
        signed_message = sign_with_jwt(message, channel_name, products)
        ws.send(json.dumps(signed_message))

    def unsubscribe_to_products(ws, products, channel_name):
        message = {
            "type": "unsubscribe",
            "channel": channel_name,
            "product_ids": products
        }
        signed_message = sign_with_jwt(message, channel_name, products)
        ws.send(json.dumps(signed_message))

    def on_open(ws):
        products = ["BTC-USD"]
        subscribe_to_products(ws, products, CHANNEL_NAMES["level2"])

    def start_websocket():
        ws = websocket.WebSocketApp(WS_API_URL, on_open=on_open, on_message=on_message)
        ws.run_forever()

    def main():
        ws_thread = threading.Thread(target=start_websocket)
        ws_thread.start()

        sent_unsub = False
        start_time = datetime.utcnow()

        try:
            while True:
                if (datetime.utcnow() - start_time).total_seconds() > 5 and not sent_unsub:
                    # Unsubscribe after 5 seconds
                    ws = websocket.create_connection(WS_API_URL)
                    unsubscribe_to_products(ws, ["BTC-USD"], CHANNEL_NAMES["level2"])
                    ws.close()
                    sent_unsub = True
                time.sleep(1)
        except Exception as e:
            print(f"Exception: {e}")

    if __name__ == "__main__":
        main()

    ```
  </Tab>
</Tabs>

## Sending Messages without API Keys

### Subscribing

```json lines wrap theme={null}
// Request
// Subscribe to ETH-USD and ETH-EUR with the level2 channel
{
  "type": "subscribe",
  "product_ids": ["ETH-USD", "ETH-EUR"],
  "channel": "level2"
}
```

### Unsubscribing

```json lines wrap theme={null}
// Request
{
  "type": "unsubscribe",
  "product_ids": ["ETH-USD", "ETH-EUR"],
  "channel": "level2"
}
```

## Sequence Numbers

Most feed messages contain a sequence number. Sequence numbers are increasing integer values for each product, with each new message being exactly one sequence number greater than the one before it.

Sequence numbers that are *greater than one integer value* from the previous number indicate that a message has been dropped. Sequence numbers that are *less* than the previous number can be ignored or represent a message that has arrived out of order.

In either situation you may need to perform logic to make sure your system is in the correct state.

<Warning>
  Even though a WebSocket connection is over TCP, the WebSocket servers receive market data in a manner that can result in dropped messages. Your feed consumer should be designed to handle sequence gaps and out of order messages, or should use channels that guarantee delivery of messages.
</Warning>

<Tip>
  To guarantee that messages are delivered and your order book is in sync, consider using the [level2 channel](/coinbase-app/advanced-trade-apis/websocket/websocket-channels#level2-channel).
</Tip>

**See Also:**

* [WebSocket Channels](/coinbase-app/advanced-trade-apis/websocket/websocket-channels)
* [WebSocket Rate Limits](/coinbase-app/advanced-trade-apis/websocket/websocket-rate-limits)
