Documentation Index
Fetch the complete documentation index at: https://docs.rivermarkets.com/llms.txt
Use this file to discover all available pages before exploring further.
WebSocket connections use the same Ed25519 keypair as the REST API — but
because browsers cannot set custom headers on the WS upgrade, the three
signing values move from headers into query params.
The official SDKs sign the handshake for you. The raw recipe below is for
integrations without an SDK.
Query parameters
| Param | Value |
|---|
key_id | Your key UUID |
ts | Current unix seconds (within 30s of server time) |
sig | Base64 Ed25519 signature over the WS canonical string |
Canonical string
LF-joined (no trailing newline). The method is the literal "WS" and there is
no body to hash:
WS\n
PATH\n
SORTED_QUERY (excluding key_id/ts/sig)\n
TIMESTAMP
SORTED_QUERY is everything except the three signing params, with keys
sorted alphabetically and values percent-encoded per RFC 3986. Empty when the
endpoint has no additional query string.
Example
import asyncio, base64, time
from urllib.parse import urlencode, quote
import websockets
from nacl.signing import SigningKey
KEY_ID = "<uuid from Settings → API Keys>"
PRIVATE_KEY_B64 = "<base64 private key shown once at creation>"
signing_key = SigningKey(base64.b64decode(PRIVATE_KEY_B64))
def sign_ws(path: str, extra_params: dict | None = None) -> str:
"""Return a fully-formed wss:// URL with signing query params appended."""
params = dict(extra_params or {})
# quote_via=quote encodes spaces as %20 to match RFC 3986 / the server.
sorted_q = urlencode(sorted(params.items()), quote_via=quote)
ts = str(int(time.time()))
canonical = "\n".join(["WS", path, sorted_q, ts]).encode()
sig = base64.b64encode(signing_key.sign(canonical).signature).decode()
params.update({"key_id": KEY_ID, "ts": ts, "sig": sig})
return f"wss://api.rivermarkets.com{path}?{urlencode(params)}"
async def stream_orders(subaccount_id: str):
url = sign_ws("/v1/ws/orders", {"subaccount_id": subaccount_id})
async with websockets.connect(url) as ws:
async for frame in ws:
print(frame)
asyncio.run(stream_orders("<subaccount-uuid>"))
Close codes
Invalid or missing auth closes the connection with code 4401. See each
endpoint’s reference page for endpoint-specific close codes.
Never share your private key or commit it to source control. Use environment
variables or a secret manager.