User Channel
This channel pushes notifications scoped to a single account whenever orders are placed, updated, cancelled, or matched into trades. Access requires L2 API credentials, supplied either as a query parameter or through an in-band JSON authentication message.
Endpoint
Section titled “Endpoint”wss://api.openfish.fun/ws/userAuthentication
Section titled “Authentication”Two authentication methods are supported:
JSON Auth (Recommended)
Section titled “JSON Auth (Recommended)”Open the WebSocket without any query parameters, then send a JSON authentication payload as the very first message. The server allows a 10-second window before timing out:
{ "auth": { "apiKey": "9180014b-33c8-9240-a14b-bdca11c0a465", "secret": "your-api-secret", "passphrase": "your-passphrase" }, "markets": []}A successful handshake returns:
{"type": "auth", "status": "ok"}If the credentials are rejected:
{"error": "invalid auth message"}If no authentication payload arrives within the deadline:
{"error": "auth timeout"}Legacy Query Parameter
Section titled “Legacy Query Parameter”For backward compatibility, pass the API key as a query parameter:
wss://api.openfish.fun/ws/user?token={api_key}The token must be a valid API key UUID. If invalid, the server responds with {"error": "invalid token"} and closes the connection.
Per-User Isolation
Section titled “Per-User Isolation”Once authenticated, the server binds a dedicated broadcast subscription to your API key. Every event delivered on the connection belongs exclusively to the authenticated account — no cross-user leakage is possible.
Heartbeat
Section titled “Heartbeat”A text message "PING" arrives from the server every 10 seconds. Respond with the text message "PONG" to prevent the connection from being dropped.
Note: This uses text messages, not WebSocket-level ping/pong frames.
Event Types
Section titled “Event Types”Produced whenever an order is placed on the book, partially filled, or removed.
| Field | Type | Description |
|---|---|---|
type | string | "order" |
order_id | string | Order ID (UUID) |
event_type | string | "PLACEMENT", "UPDATE", or "CANCELLATION" |
status | string | "LIVE", "MATCHED", "CANCELED" |
market | string | Condition ID |
asset_id | string | Token ID |
side | string | "BUY" or "SELL" |
price | string | Order price |
original_size | string | Original size |
size_matched | string | Amount matched so far |
outcome | string | "YES" or "NO" |
order_type | string | "GTC", "FOK", "GTD" |
owner | string | API key of the order owner |
timestamp | integer | Unix timestamp |
Order Placement:
{ "type": "order", "order_id": "0xff354cd7ca7539dfa9c28d90943ab5779a4eac34b9b37a757d7b32bdfb11790b", "event_type": "PLACEMENT", "status": "LIVE", "asset_id": "52114319501245915516055106046884209969926127482827954674443846427813813222426", "market": "0xbd31dc8a20211944f6b70f31557f1001557b59905b7738480ca09bd4532f84af", "outcome": "YES", "order_type": "GTC", "side": "SELL", "price": "0.57", "original_size": "10", "size_matched": "0", "owner": "9180014b-33c8-9240-a14b-bdca11c0a465", "timestamp": 1672290687}Partial Fill:
{ "type": "order", "order_id": "0xff354cd7...", "event_type": "UPDATE", "status": "LIVE", "size_matched": "5", "original_size": "10", "timestamp": 1672290700}Cancellation:
{ "type": "order", "order_id": "0xff354cd7...", "event_type": "CANCELLATION", "status": "CANCELED", "timestamp": 1672290710}Produced when one of your orders participates in a match, and again each time the resulting trade advances through its settlement lifecycle.
| Field | Type | Description |
|---|---|---|
type | string | "trade" |
trade_id | string | Trade UUID |
taker_order_id | string | Taker’s order ID |
market | string | Condition ID |
asset_id | string | Token ID |
side | string | "BUY" or "SELL" |
price | string | Trade price |
size | string | Trade size |
fee | string | Fee amount |
status | string | "MATCHED", "CONFIRMED", "FAILED" |
trader_side | string | "TAKER" or "MAKER" — your role in the trade |
maker_orders | array | Details of matched maker orders |
timestamp | integer | Unix timestamp |
{ "type": "trade", "trade_id": "28c4d2eb-bbea-40e7-a9f0-b2fdb56b2c2e", "taker_order_id": "0x06bc63e3...", "asset_id": "52114319...", "market": "0xbd31dc8a...", "side": "BUY", "price": "0.57", "size": "10", "fee": "0.0057", "status": "MATCHED", "trader_side": "TAKER", "maker_orders": [ { "order_id": "0xff354cd7...", "owner": "9180014b-...", "maker_address": "0xf39F...", "matched_amount": "10" } ], "timestamp": 1672290701}Trade Lifecycle
Section titled “Trade Lifecycle”Every trade moves through a sequence of statuses as it progresses from in-memory match to on-chain finality:
MATCHED --> MINED --> CONFIRMED | ^ v |RETRYING ----+ | v FAILED| Status | Terminal | Description |
|---|---|---|
MATCHED | No | Trade matched by the CLOB engine, sent to executor |
MINED | No | Transaction observed on-chain, not yet finalized |
CONFIRMED | Yes | Transaction achieved finality |
RETRYING | No | Transaction failed (revert/reorg), being resubmitted |
FAILED | Yes | Trade permanently failed after retry exhaustion |
Each status transition triggers a new trade event on the WebSocket.
Per-User Isolation
Section titled “Per-User Isolation”Internally, the broadcast system maintains a DashMap keyed by API key. Every authenticated connection receives its own tokio::sync::broadcast channel, guaranteeing that events published for one user never leak to another. When a client disconnects, its channel is cleaned up automatically.
Rust SDK Example
Section titled “Rust SDK Example”use openfish_client_sdk::ws::UserSocket;use futures_util::StreamExt;
#[tokio::main]async fn main() -> anyhow::Result<()> { let api_key = "your-api-key-uuid"; let url = format!("wss://api.openfish.fun/ws/user?token={}", api_key);
let mut ws = UserSocket::connect(&url).await?;
while let Some(event) = ws.next().await { match event.event_type.as_str() { "order" => { println!( "Order {}: {} {} @ {}", event.r#type, event.side, event.original_size, event.price ); } "trade" => { println!( "Trade {}: {} {} @ {} [{}]", event.id, event.side, event.size, event.price, event.status ); } _ => {} } }
Ok(())}Security Considerations
Section titled “Security Considerations”- Keep your API key out of client-side browser code. The user channel is designed for server-side applications and native clients only.
- API keys are validated as UUIDs against the database each time a connection is opened. Rotate credentials immediately if you suspect they have been compromised.
- Authentication failures cause the server to close the WebSocket right away, preventing any events from being sent before the connection terminates.
Next Steps
Section titled “Next Steps”- Market Channel — Public order book and trade events.
- WebSocket Overview — All available channels.