Skip to main content

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

ParamValue
key_idYour key UUID
tsCurrent unix seconds (within 30s of server time)
sigBase64 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.