Skip to main content

Prime WebSocket Channels

Heartbeats Channel​

// Request -> heartbeats channel
{
"type": "subscribe",
"channel": "heartbeats",
"access_key": "ACCESS_KEY",
"api_key_id": "SVC_ACCOUNTID",
"timestamp": "TIMESTAMP",
"passphrase": "PASSPHRASE",
"signature": "SIGNATURE",
"portfolio_id": "PORTFOLIO_ID",
"product_ids": [
"BTC-USD"
]
}

Heartbeats indicate the current timestamp, as well as the number of messages sent.

// Response ->  heartbeats channel
{
"channel": "subscriptions",
"timestamp": "2022-01-25T20:52:59.353824785Z",
"sequence_num": 0,
"events": [
{
"subscriptions": {
"heartbeats": [
"heartbeats"
]
}
}
]
}

Orders Channel​

The orders channel provides real-time updates on orders you've made.

//Request -> orders channel
{
"type": "subscribe",
"channel": "orders",
"access_key": "ACCESS_KEY",
"api_key_id": "SVC_ACCOUNTID",
"timestamp": "TIMESTAMP",
"passphrase": "PASSPHRASE",
"signature": "SIGNATURE",
"portfolio_id": "PORTFOLIO_ID",
"product_ids": [
"BTC-USD"
]
}
//Response -> orders channel 
{
"channel": "orders",
"timestamp": "2022-01-25T21:16:07.366595573Z",
"sequence_num": 0,
"events": [
{
"type": "snapshot",
"orders": [
{
"order_id": "4c62681b-be8a-439c-af2b-5f0100386cc0",
"client_order_id": "20a8cbe0-7680-4eba-9ffd-f9c2de89035d",
"cum_qty": "0",
"leaves_qty": "3",
"avg_px": "0",
"fees": "0",
"status": "OPEN"
}
]
}
]
}
{
"channel": "subscriptions",
"timestamp": "2022-01-25T21:16:07.366625018Z",
"sequence_num": 1,
"events": [
{
"subscriptions": {
"orders": [
"PORTFOLIO_ID"
]
}
}
]
}

Python Example​

Below is a detailed end to end Python script for subscribing to the orders channel:

import asyncio, base64, hashlib, hmac, json, os, sys, time, websockets

PASSPHRASE = os.environ.get('PASSPHRASE')
ACCESS_KEY = os.environ.get('ACCESS_KEY')
SECRET_KEY = os.environ.get('SIGNING_KEY')
SVC_ACCOUNTID = os.environ.get('SVC_ACCOUNTID')
PORTFOLIO_ID = os.environ.get('PORTFOLIO_ID')

uri = 'wss://ws-feed.prime.coinbase.com'
timestamp = str(int(time.time()))
channel = 'orders'
product_id = 'ETH-USD'

async def main_loop():
while True:
try:
async with websockets.connect(uri, ping_interval=None, max_size=None) as websocket:
signature = sign(channel, ACCESS_KEY, SECRET_KEY, SVC_ACCOUNTID, PORTFOLIO_ID, product_id)
auth_message = json.dumps({
'type': 'subscribe',
'channel': channel,
'access_key': ACCESS_KEY,
'api_key_id': SVC_ACCOUNTID,
'timestamp': timestamp,
'passphrase': PASSPHRASE,
'signature': signature,
'portfolio_id': PORTFOLIO_ID,
'product_ids': [product_id]
})
await websocket.send(auth_message)
while True:
response = await websocket.recv()
parsed_response = json.loads(response)
print(json.dumps(parsed_response, indent=3))
except websockets.ConnectionClosed:
continue

def sign(channel, key, secret, account_id, PORTFOLIO_ID, product_ids):
message = channel + key + account_id + timestamp + PORTFOLIO_ID + product_ids
signature = hmac.new(SECRET_KEY.encode('utf-8'), message.encode('utf-8'), digestmod=hashlib.sha256).digest()
signature_b64 = base64.b64encode(signature).decode()
return signature_b64

try:
asyncio.get_event_loop().run_until_complete(main_loop())
except KeyboardInterrupt:
print('\nClosing Prime websocket feed')
sys.exit()

This script is open source and available on GitHub.

Level2 Data Channel​

The l2_data channel guarantees delivery of all updates and is the easiest way to keep a snapshot of the order book.

// Request -> l2_data channel
{
"type": "subscribe",
"channel": "l2_data",
"access_key": "ACCESS_KEY",
"api_key_id": "SVC_ACCOUNTID",
"timestamp": "TIMESTAMP",
"passphrase": "PASSPHRASE",
"signature": "SIGNATURE",
"portfolio_id": "",
"product_ids": [
"BTC-USD"
]
}

The channel returns an array of dictionaries containing the order's side, event time, price, and quantity. The first message is a snapshot of the current order book.

//Response -> l2_data channel
{
"channel":"l2_data",
"timestamp":"2022-06-15T01:52:23.408309238Z",
"sequence_num":2,
"events":[
{
"type":"update",
"product_id":"BTC-USD",
"updates":[
{
"side":"bid",
"event_time":"2022-06-15T01:52:23.385605Z",
"px":"22145.3",
"qty":"0.01083825"
},
{
"side":"offer",
"event_time":"2022-06-15T01:52:23.385605Z",
"px":"22149.26",
"qty":"0.03450782"
}
]
}
]
}
[
{
"channel": "subscriptions",
"timestamp": "2022-02-16T22:56:34.770593004Z",
"sequence_num": 0,
"events": [
{
"subscriptions": {
"l2_data": [
"BTC-USD"
]
}
}
]
},
{
"side": "offer",
"event_time": "2022-02-16T22:58:15.586676771Z",
"px": "22500",
"qty": "0.208"
},
{
"side": "offer",
"event_time": "2022-02-16T22:58:15.586676771Z",
"px": "22500.15",
"qty": "0.00316"
}
]

Python Example​

Below is a detailed end to end Python script for subscribing to the l2_data channel:

import asyncio, base64, hashlib, hmac, json, os, sys, time, websockets

PASSPHRASE = os.environ.get('PASSPHRASE')
ACCESS_KEY = os.environ.get('ACCESS_KEY')
SECRET_KEY = os.environ.get('SIGNING_KEY')
SVC_ACCOUNTID = os.environ.get('SVC_ACCOUNTID')

uri = 'wss://ws-feed.prime.coinbase.com'
timestamp = str(int(time.time()))
channel = 'l2_data'
product_ids = ['ETH-USD']

async def main_loop():
while True:
try:
async with websockets.connect(uri, ping_interval=None, max_size=None) as websocket:
signature = sign(channel, ACCESS_KEY, SECRET_KEY, SVC_ACCOUNTID, product_ids)
auth_message = json.dumps({
'type': 'subscribe',
'channel': channel,
'access_key': ACCESS_KEY,
'api_key_id': SVC_ACCOUNTID,
'timestamp': timestamp,
'passphrase': PASSPHRASE,
'signature': signature,
'product_ids': product_ids
})
await websocket.send(auth_message)
while True:
response = await websocket.recv()
parsed_response = json.loads(response)
print(json.dumps(parsed_response, indent=3))
except websockets.ConnectionClosed:
continue

def sign(channel, key, secret, account_id, product_ids):
message = channel + key + account_id + timestamp + "".join(product_ids)
signature = hmac.new(SECRET_KEY.encode('utf-8'), message.encode('utf-8'), digestmod=hashlib.sha256).digest()
signature_b64 = base64.b64encode(signature).decode()
return signature_b64

try:
asyncio.get_event_loop().run_until_complete(main_loop())
except KeyboardInterrupt:
print('\nClosing Prime websocket feed')
sys.exit()

This script is open source and available on GitHub.

Maintaining an Order Book​

For a detailed look at building and maintaining an order book using the L2 data market feed, refer to this reference application.

Calculating Slippage​

The following code sample demonstrates how you can use the Websocket feed to calculate slippage.

#PYTHON EXAMPLE
#!/usr/bin/env python
import asyncio
import datetime
import json, hmac, hashlib, time, base64
import asyncio
import time
import websockets
import unittest
import logging
import sys
PASSPHRASE = "<API key passphrase here>"
ACCESS_KEY = "<API access key here>"
SIGNING_KEY = "<API signing key here>"
SVC_ACCOUNTID = "<API account ID passphrase here>"
s = time.gmtime(time.time())
TIMESTAMP = time.strftime("%Y-%m-%dT%H:%M:%SZ",s)
"""
A processor maintains an in-memory price book used for analytics
"""
class PriceBookProcessor:
def __init__(self, snapshot):
"""
Instantiate a processor using a snapshot from the Websocket feed
"""
self.bids = []
self.offers = []
snapshot_data = json.loads(snapshot)
px_levels = snapshot_data["events"][0]["updates"]
for i in range(len(px_levels)):
level = px_levels[i]
if level["side"] == "bid":
self.bids.append(level)
elif level["side"] == "offer":
self.offers.append(level)
else:
raise IOError()
self._sort()
def apply_update(self, data):
"""
Update in-memory state with a single update from the Websocket feed
"""
event = json.loads(data)
if event["channel"] != "l2_data":
return
events = event["events"]
for e in events:
updates = e["updates"]
for update in updates:
self._apply(update)
self._filter_closed()
self._sort()
def _apply(self, level):
if level["side"] == "bid":
found = False
for i in range(len(self.bids)):
if self.bids[i]["px"] == level["px"]:
self.bids[i] = level
found = True
break
if not found:
self.bids.append(level)
else:
found = False
for i in range(len(self.offers)):
if self.offers[i]["px"] == level["px"]:
self.offers[i] = level
found = True
break
if not found:
self.offers.append(level)
def _filter_closed(self):
self.bids = [x for x in self.bids if abs(float(x["qty"])) > 0]
self.offers = [x for x in self.offers if abs(float(x["qty"])) > 0]
def _sort(self):
self.bids = sorted(self.bids, key=lambda x: float(x["px"]) * -1)
self.offers = sorted(self.offers, key=lambda x: float(x["px"]))
def stringify(self):
"""
Return a string summary of the contents of the price book
"""
best_bid = self.bids[0]["px"]
best_offer = self.offers[0]["px"]
spread = str(float(best_offer) - float(best_bid))
l1 = f"{best_bid} =>{spread}<= {best_offer} ({len(self.bids)} bids, {len(self.offers)} offers)\n"
bids = self.bids[:5]
offers = self.offers[:5]
l2, l3 = "", ""
if len(bids) > 0:
l2 = "Bids: " + ", ".join([b["qty"] + " " + b["px"] for b in bids]) + "\n"
if len(offers) > 0:
l3 = "Offers: " + ", ".join([b["qty"] + " " + b["px"] for b in offers]) + "\n"
l4 = "Buy 1 BTC: " + str(self.estimate_aggressive_px(1.0, True)) + " USD\n"
return l1 + l2 + l3 + l4
def estimate_aggressive_px(self, qty, is_bid=True):
"""
Estimate the average price of an aggressive order of a given size/side
"""
orders = self.bids
if is_bid:
orders = self.offers
total, total_value = 0.0, 0.0
idx = 0
while total < qty and idx < len(orders):
px = float(orders[idx]["px"])
this_level = min(qty-total, float(orders[idx]["qty"]))
value = this_level * px
total_value += value
total += this_level
idx += 1
return total_value / total
"""
Sign a subscription for the Websocket API
"""
async def sign(channel, key, secret, account_id, portfolio_id, product_ids):
message = channel + key + account_id + TIMESTAMP + portfolio_id + product_ids
print(message)
signature = hmac.new(secret.encode('utf-8'), message.encode('utf-8'), digestmod=hashlib.sha256).digest()
signature_b64 = base64.b64encode(signature).decode()
print(signature_b64)
return signature_b64
"""
Main loop for consuming from the Websocket feed
"""
async def main_loop():
uri = "wss://ws-feed.prime.coinbase.com"
async with websockets.connect(uri, ping_interval=None, max_size=None) as websocket:
signature = await sign('l2_data', ACCESS_KEY, SIGNING_KEY, SVC_ACCOUNTID, "", "BTC-USD")
print(signature)
auth_message = json.dumps({
"type": "subscribe",
"channel": "l2_data",
"access_key": ACCESS_KEY,
"api_key_id": SVC_ACCOUNTID,
"timestamp": TIMESTAMP,
"passphrase": PASSPHRASE,
"signature": signature,
"portfolio_id": "",
"product_ids": ["BTC-USD"],
})
print(type(auth_message))
print(auth_message)
await websocket.send(auth_message)
try:
processor = None
while True:
response = await websocket.recv()
#print(f"<<< {response}")
parsed = json.loads(response)
if parsed["channel"] == "l2_data" and parsed["events"][0]["type"] == "snapshot":
processor = PriceBookProcessor(response)
elif processor != None:
processor.apply_update(response)
if processor != None:
print(processor.stringify())
sys.stdout.flush()
except websockets.exceptions.ConnectionClosedError:
print("Error caught")
sys.exit(1)
if __name__ == '__main__':
asyncio.run(main_loop())

Was this helpful?