Skip to main content
The Price API streams real-time indicative prices from Bebop’s market makers over WebSocket using protobuf-encoded messages. This guide walks you through connecting, decoding, and processing the stream.
What you’ll build: A WebSocket client that receives and decodes real-time pricing data.Time required: 10-15 minutesPrerequisites: Python 3.10+, uv (or pip), and an API key from Bebop.

1. Set Up Protobuf

The Price API encodes messages using Protocol Buffers for compact, low-latency delivery. You’ll need to generate Python bindings from the schema.

Define the Schema

Create bebop.proto:
syntax = "proto3";
package bebop;

message PriceUpdate {
  optional bytes  base            = 1;
  optional bytes  quote           = 2;
  optional uint64 last_update_ts  = 3;
  repeated float  bids            = 4 [packed = true];
  repeated float  asks            = 5 [packed = true];
}

message BebopPricingUpdate {
  repeated PriceUpdate pairs = 1;
}
Each PriceUpdate contains:
FieldDescription
baseBase token contract address (raw bytes)
quoteQuote token contract address (raw bytes)
last_update_tsTimestamp of the last price update (milliseconds)
bidsFlat array of floats: [price₁, size₁, price₂, size₂, ...]
asksFlat array of floats: [price₁, size₁, price₂, size₂, ...]

Generate Python Bindings

Install the protobuf compiler tools and generate the Python module:
uv pip install grpcio-tools protobuf

python -m grpc_tools.protoc \
  --proto_path=. \
  --python_out=. \
  bebop.proto
This produces bebop_pb2.py - the module you’ll import to decode messages.

2. Connect to the WebSocket

The pricing stream is available at:
wss://api.bebop.xyz/pmm/{network}/v3/pricing

Connection Parameters

ParameterRequiredDescription
formatYesSet to protobuf
nameYesYour integration name (for identification)
authorizationYesYour API key
gaslessNofalse for self-execution quotes (default), true for gasless quotes
expiry_typeNoshort or standard (default) - controls quote expiry window

Open the Connection

import asyncio

import websockets

from bebop_pb2 import BebopPricingUpdate  # type: ignore

NETWORK = "ethereum"
USERNAME = "<you_username>"
API_KEY = "<your_api_key>"

WSS_URL = (
    f"wss://api.bebop.xyz/pmm/{NETWORK}/v3/pricing"
    f"?format=protobuf"
    f"&name={USERNAME}"
    f"&authorization={API_KEY}"
    f"&gasless=false"
    f"&expiry_type=standard"
)

async def main():
    async with websockets.connect(
        WSS_URL, ping_interval=20, ping_timeout=10, max_size=2**21
    ) as ws:
        print(f"Connected to pricing stream on {NETWORK}")

        async for blob in ws:
            update = BebopPricingUpdate()
            update.ParseFromString(blob)
            print(f"Received {len(update.pairs)} pairs")
asyncio.run(main())

3. Decode the Price Data

Each message contains updates for multiple trading pairs. To extract pricing for a specific pair, match on the base and quote addresses.

Resolve Token Addresses

(optional) First, look up the contract addresses for the tokens you want:
async def fetch_token_list(network: str) -> dict:
    url = f"https://api.bebop.xyz/pmm/{network}/v3/tokenlist"
    resp = httpx.get(url, timeout=10.0)
    resp.raise_for_status()
    return resp.json().get("tokens", {})

tokens_raw = asyncio.run(fetch_token_list(NETWORK))
tokens = {t["symbol"]: t for t in tokens_raw}

# Example: WETH/USDC
base_addr = tokens["WETH"]["address"].lower()
quote_addr = tokens["USDC"]["address"].lower()

Parse Bids and Asks

The bids and asks fields are flat float arrays encoding (price, size) pairs:
def address_to_hex(b: bytes) -> str:
    """Convert raw protobuf bytes to a hex address."""
    return "0x" + b.hex()

def to_levels(flat: list[float]) -> list[tuple[float, float]]:
    """Unpack flat [price, size, price, size, ...] into level tuples."""
    it = iter(flat)
    return list(zip(it, it, strict=True))

async def stream_prices(pair_base: str, pair_quote: str):
    async with websockets.connect(
        WSS_URL, ping_interval=20, ping_timeout=10, max_size=2**21
    ) as ws:
        async for blob in ws:
            update = BebopPricingUpdate()
            update.ParseFromString(blob)

            for pair in update.pairs:
                base_hex = address_to_hex(pair.base)
                quote_hex = address_to_hex(pair.quote)

                if base_hex.lower() != pair_base or quote_hex.lower() != pair_quote:
                    continue

                bids = to_levels(list(pair.bids))
                asks = to_levels(list(pair.asks))

                if not bids or not asks:
                    continue

                best_bid = bids[0][0]
                best_ask = asks[0][0]
                mid_price = (best_bid + best_ask) / 2
                spread_bps = (best_ask - best_bid) / mid_price * 10_000

                print(
                    f"WETH/USDC  mid: {mid_price:.2f}  "
                    f"spread: {spread_bps:.1f} bps  "
                    f"bid levels: {len(bids)}  "
                    f"ask levels: {len(asks)}"
                )

asyncio.run(stream_prices(base_addr, quote_addr))

Understanding the Levels

Bids are ordered best (highest) first, asks are ordered best (lowest) first. Each level represents a price point with available size in base token units. For example, if bids = [2450.50, 1.5, 2449.80, 3.0], that means:
LevelPriceSize
12450.501.5 WETH
22449.803.0 WETH

4. Full Example

Putting it all together - a complete script that connects, resolves a trading pair, and prints live pricing:
import asyncio

import httpx
import websockets

from bebop_pb2 import BebopPricingUpdate  # type: ignore

NETWORK = "ethereum"
USERNAME = "<you_username>"
API_KEY = "<your_api_key>"

WSS_URL = (
    f"wss://api.bebop.xyz/pmm/{NETWORK}/v3/pricing"
    f"?format=protobuf"
    f"&name={USERNAME}"
    f"&authorization={API_KEY}"
    f"&gasless=false"
    f"&expiry_type=standard"
)

PAIR = "WETH/USDC"

def address_to_hex(b: bytes) -> str:
    return "0x" + b.hex()

def to_levels(flat: list[float]) -> list[tuple[float, float]]:
    it = iter(flat)
    return list(zip(it, it, strict=True))

# 1. Resolve token addresses
resp = httpx.get(f"https://api.bebop.xyz/pmm/{NETWORK}/v3/tokenlist", timeout=10.0)
tokens = {t["symbol"]: t for t in resp.json().get("tokens", {})}
base_symbol, quote_symbol = PAIR.split("/")
base_addr = tokens[base_symbol]["address"].lower()
quote_addr = tokens[quote_symbol]["address"].lower()

# 2. Connect to the pricing stream

async def main():
    async with websockets.connect(
        WSS_URL, ping_interval=20, ping_timeout=10, max_size=2**21
    ) as ws:
        print(f"Connected - streaming {PAIR} on {NETWORK}\n")

        async for blob in ws:
            update = BebopPricingUpdate()
            update.ParseFromString(blob)

            for pair in update.pairs:
                if (
                    address_to_hex(pair.base).lower() != base_addr
                    or address_to_hex(pair.quote).lower() != quote_addr
                ):
                    continue

                bids = to_levels(list(pair.bids))
                asks = to_levels(list(pair.asks))

                if not bids or not asks:
                    continue

                best_bid = bids[0][0]
                best_ask = asks[0][0]
                mid = (best_bid + best_ask) / 2
                spread = (best_ask - best_bid) / mid * 10_000

                print(
                    f"{PAIR}  mid: {mid:.2f}  "
                    f"spread: {spread:.1f} bps  "
                    f"best bid: {best_bid:.2f}  "
                    f"best ask: {best_ask:.2f}  "
                    f"levels: {len(bids)}b / {len(asks)}a"
                )
asyncio.run(main())
Prices are indicative. The Price API streams real-time market data for monitoring and pre-trade analysis. To get firm, executable quotes, use the RFQ API.

Next Steps

RFQ API Quickstart

Execute trades against the prices you’re streaming.

Price API Reference

Message types, connection limits, and coverage details.