Skip to main content
Authenticated market makers receive RFQ broadcasts:
{
  "type": "rfq",
  "data": "base64-no-padding RFQ bytes"
}
type
string
Always "rfq".
data
string
256 bytes of BroadcastRfqRequest, encoded as standard base64 without padding.
Skip RFQs where the decoded expires_at_ms has already passed by the time you finish decoding.

Message Loop

Once Authentication returns a session_token, attach a message loop to the same WebSocket and send a subscribe message for the RFQ categories this connection should receive. Authenticated connections receive no RFQs until they subscribe. Subscribe messages are additive for the connection: sending another subscribe message adds filters, and all filters are dropped when the socket disconnects. The loop also needs to reply to server ping heartbeats to keep the session alive. A successful subscribe returns a subscribed frame. decode_rfq is defined in Decode Example below.
{
  "type": "subscribe",
  "subscriptions": [
    { "kind": "all" }
  ]
}
Use { "kind": "mention" } for RFQs with mention-market legs, or { "kind": "price", "asset": "BTC" } for RFQs with a price leg for one asset. Price assets use the normal ticker symbols: BTC, ETH, SOL, XRP, HYPE.
import json


async def receive_rfqs(ws) -> None:
    """Call after the Authentication handshake returns `session_token`."""
    await ws.send(json.dumps({
        "type": "subscribe",
        "subscriptions": [{"kind": "all"}],
    }))

    async for raw in ws:
        msg = json.loads(raw)
        mtype = msg.get("type")
        if mtype == "subscribed":
            continue
        elif mtype == "rfq":
            rfq = decode_rfq(msg["data"])
            print(
                f"RFQ {rfq.request_id} wager={rfq.wager_micros} "
                f"legs={len(rfq.legs)} expires_at={rfq.expires_at_ms}"
            )
        elif mtype == "ping":
            await ws.send(json.dumps({"type": "pong"}))

BroadcastRfqRequest

Decoded data is a 256-byte struct. All integer fields are little-endian; UUID fields are raw 16-byte UUIDs.
OffsetBFieldTypeMeaning
016request_iduuidRFQ UUID
168wager_microsuint64 LEuser wager in USDC micros
248expires_at_msuint64 LERFQ expiry as Unix milliseconds
3224taker_metadatastructoptional taker tier and wallet
561order_typeuint81=IOC, 2=FOK
571leg_countuint8number of active legs (1..8)
586reservedzero padding
64192legsstruct[8]eight 24-byte RfqLeg slots
Only the first leg_count leg slots are active; the rest are zeroed.

taker_metadata (24 bytes)

OffsetBFieldTypeMeaning
01optionuint80 = absent; non-zero = present
11tieruint80=Standard, 1=Silver, 2=Gold, 3=Platinum, 4=VIP
22reservedzero padding
420addressbytes20raw EVM taker wallet address

RfqLeg (24 bytes)

OffsetBFieldTypeMeaning
08market_iduint64 LELongshot market id
88start_at_msuint64 LEcanonical market start time (Unix ms)
161market_kinduint80=Price, 1=Mention
171directionuint80=UP, 1=DOWN
181leg_indexuint8leg position in the parlay
191price_assetuint80=BTC, 1=ETH, 2=SOL, 3=XRP, 4=HYPE; 0 on mention legs
204price_duration_secsuint32 LEsupported: 60, 300, 900, 3600, 14400, 86400; 0 on mention legs
Price legs (market_kind = 0) populate price_asset and price_duration_secs with the underlying asset code and duration. Mention legs (market_kind = 1) reference externally-sourced markets and leave both fields zero; the leg’s identity is fully determined by market_id.

Decode Example

decode_rfq turns the base64-no-padding data field into a typed BroadcastRfqRequest matching the byte layout above.
import base64
from dataclasses import dataclass
from typing import Optional
from uuid import UUID


@dataclass
class TakerMetadata:
    tier: int
    address: bytes  # 20 raw bytes


@dataclass
class RfqLeg:
    market_id: int
    start_at_ms: int
    market_kind: int          # 0=Price, 1=Mention
    direction: int            # 0=UP, 1=DOWN
    leg_index: int
    price_asset: int          # 0=BTC, 1=ETH, 2=SOL, 3=XRP, 4=HYPE; 0 on mention legs
    price_duration_secs: int  # 0 on mention legs


@dataclass
class BroadcastRfqRequest:
    request_id: UUID
    wager_micros: int
    expires_at_ms: int
    taker_metadata: Optional[TakerMetadata]
    order_type: int  # 1=IOC, 2=FOK
    legs: list[RfqLeg]


def decode_rfq(data_b64: str) -> BroadcastRfqRequest:
    pad = (-len(data_b64)) % 4
    payload = base64.b64decode(data_b64 + "=" * pad)
    if len(payload) != 256:
        raise ValueError(f"RFQ payload must be 256 bytes, got {len(payload)}")

    request_id = UUID(bytes=bytes(payload[0:16]))
    wager_micros = int.from_bytes(payload[16:24], "little")
    expires_at_ms = int.from_bytes(payload[24:32], "little")

    meta = payload[32:56]
    taker_metadata = None
    if meta[0] != 0:
        taker_metadata = TakerMetadata(tier=meta[1], address=bytes(meta[4:24]))

    order_type = payload[56]
    leg_count = payload[57]

    legs: list[RfqLeg] = []
    for i in range(leg_count):
        off = 64 + i * 24
        slot = payload[off:off + 24]
        legs.append(RfqLeg(
            market_id=int.from_bytes(slot[0:8], "little"),
            start_at_ms=int.from_bytes(slot[8:16], "little"),
            market_kind=slot[16],
            direction=slot[17],
            leg_index=slot[18],
            price_asset=slot[19],
            price_duration_secs=int.from_bytes(slot[20:24], "little"),
        ))

    return BroadcastRfqRequest(
        request_id=request_id,
        wager_micros=wager_micros,
        expires_at_ms=expires_at_ms,
        taker_metadata=taker_metadata,
        order_type=order_type,
        legs=legs,
    )

Example RFQ

A 3-leg BTC UP / UP / UP parlay across three consecutive 5-minute windows.
BTC UP / UP / UP parlay built in the Longshot UI showing three consecutive 5-minute windows

On the wire

The server broadcasts an rfq envelope; data is the 256-byte BroadcastRfqRequest encoded as standard base64 with no padding (⌈256 × 4 / 3⌉ → 342 characters).
{
  "type": "rfq",
  "data": "ERERESIiMzNERFVVVVVVVYCWmAAAAAAAZ8dcro4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwAAAAAAAOkDAAAAAAAAYMhcro4BAAAAAAAALAEAAOoDAAAAAAAAQFxhro4BAAAAAAEALAEAAOsDAAAAAAAAIPBlro4BAAAAAAIALAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}

Decoded BroadcastRfqRequest

Running the decode example on data yields:
{
  "request_id": "11111111-2222-3333-4444-555555555555",
  "wager_micros": 10000000,
  "expires_at_ms": 1712322299751,
  "taker_metadata": null,
  "order_type": 2,
  "leg_count": 3,
  "legs": [
    { "leg_index": 0, "market_kind": 0, "direction": 0, "price_asset": 0, "price_duration_secs": 300, "market_id": 1001, "start_at_ms": 1712322300000 },
    { "leg_index": 1, "market_kind": 0, "direction": 0, "price_asset": 0, "price_duration_secs": 300, "market_id": 1002, "start_at_ms": 1712322600000 },
    { "leg_index": 2, "market_kind": 0, "direction": 0, "price_asset": 0, "price_duration_secs": 300, "market_id": 1003, "start_at_ms": 1712322900000 }
  ]
}

Legs at a glance

#AssetDirectionWindowstart_at_ms (UTC)market_id
0 BTCUP5 min1712322300000 — 2024-04-05 13:05:00Z1001
1 BTCUP5 min1712322600000 — 2024-04-05 13:10:00Z1002
2 BTCUP5 min1712322900000 — 2024-04-05 13:15:00Z1003
The parlay pays out only if all three windows close UP. Wager is 10 USDC (wager_micros = 10_000_000) and the order is FOK (order_type = 2).

Error Codes

Session-level failures arrive as an error frame on the WebSocket:
{ "type": "error", "code": "AUTH_EXPIRED", "message": "session expired" }
Any of these can interrupt your receive loop. Most require a reconnect and re-authentication; see the Action column.
CodeMeaningAction
AUTH_EXPIREDSession is older than 1 hour.Reconnect and re-auth.
HEARTBEAT_TIMEOUTServer missed 3 consecutive pongs; connection will close.Reconnect and re-auth; pong within 15s of each ping.
AUTH_TIMEOUTDid not complete the auth handshake within 10 seconds of connecting.Reconnect; finish handshake faster.
NOT_AUTHENTICATEDSent a non-auth message before .Complete auth first.
ALREADY_AUTHENTICATEDSent auth or auth_response after already authenticating.Stop re-authing on the same connection.
AUTH_BANNEDYour IP address was temporarily banned after repeated failed auth attempts. Ban is tracked per IP in the gateway (not per wallet) and expires after a TTL.Wait for the ban TTL to lift; reconnecting from a different IP also works.
AUTH_UNAVAILABLEAuth backend is temporarily unavailable.Back off and retry.
MM_CONNECTION_LIMITToo many authenticated WebSocket connections for this market maker.Close an existing connection or reduce parallel sockets before retrying.
UNKNOWN_MMQuote rate-limiter could not find the authenticated MM in the registry (e.g. removed mid-session). Note: an unregistered wallet at the auth handshake fails earlier as , not this frame.Reconnect and re-auth; if it persists, contact Longshot ops to provision the wallet.
MALFORMED_JSONA message you sent failed JSON parsing.Fix the message; no reconnect required.
BINARY_NOT_SUPPORTEDServer received a binary WebSocket frame.Send JSON text frames only.
RATE_LIMITEDToo many control messages (e.g. subscribe, repeated auth). Quote throttling uses the rate_limit frame instead — see Rate Limits.Back off; respect the operational limits on Authentication.
CONNECTION_LIMITGlobal WebSocket connection cap reached.Reduce concurrent connections; retry later.
UNAUTH_LIMITToo many unauthenticated connections pending.Retry after authenticating existing connections.
IP_LIMITToo many connections from your IP.Reduce per-IP concurrency.