Skip to content

WebSocket Events

The app doesn't poll. When something happens — a proof finishes, a position moves, a market shifts — the backend pushes that event to your browser over a persistent WebSocket connection. This is how the UI stays alive without you refreshing the page.

Connecting

The WebSocket endpoint lives on the zkde backend (port 8003), not a separate service:

EnvironmentURL
Devws://localhost:8003/ws/{user_address}
Prodwss://zkde.fi/ws/{user_address}

The {user_address} in the URL scopes the connection to your wallet. You'll only receive events relevant to your address, plus broadcast events like market changes that go to everyone.

Frontend hook

The app uses a custom React hook that handles connection lifecycle, reconnection, and selective subscription:

javascript
import { useWebSocket } from "@/hooks/useWebSocket";

const { connected, subscribe } = useWebSocket(address);

useEffect(() => {
  const unsub = subscribe("proof_complete", (data) => {
    console.log("Proof done:", data);
  });
  return unsub;
}, []);

You can subscribe to individual event types or use "*" as a wildcard to catch everything. Each subscribe() call returns an unsubscribe function — call it in your cleanup.

Event types

EventWhen it firesKey fields
strategy_updateA strategy is created, modified, or scoredstrategy_id, pool_id, apy, risk_score
market_changeSignificant price or rate movement in a tracked poolchange_type, pool_id, old_value, new_value, change_pct
alertA position crosses a threshold (drawdown, exposure, health factor)severity, alert_type, message, action
proof_completeA proof finishes generating and is ready for verificationproof_type, proof_hash, success
position_updateA position's value changes (from market movement or execution)position_id, current_value
agent_status_changeAn agent starts, stops, pauses, errors, or resumesagent_id, status

The proof_complete event is the one you'll care about most during development — it tells you when a proof is ready to be consumed by the next stage of the pipeline.

What's happening behind the scenes

flowchart LR
  MP["Market poller<br/><i>every 60s</i>"] --> EB["Event bus<br/><i>internal pub/sub</i>"]
  PM["Position monitor<br/><i>every 5min</i>"] --> EB
  PS["Proof services<br/><i>on completion</i>"] --> EB
  EB --> WSM["WebSocket manager"]
  WSM --> FE1["Browser: user A"]
  WSM --> FE2["Browser: user B"]
  WSM --> FE3["Browser: user C"]

Workers run on independent cycles. The market poller checks pool rates and token prices every 60 seconds. The position monitor evaluates portfolio health every 5 minutes. Proof services emit events when proofs complete — these are not periodic, they fire on completion.

All of these feed into an internal event bus (simple pub/sub within the FastAPI process), which the WebSocket manager reads from and fans out to connected clients based on address matching and subscription filters.

Connection behavior

The frontend hook handles reconnection automatically, but it's worth knowing the rules:

  • Auto-reconnect: exponential backoff starting at 1s, capped at ~30s, max 10 attempts before giving up
  • Keep-alive: ping/pong every 30 seconds — if the server doesn't see a pong, it drops the connection
  • Selective subscriptions: subscribe to specific event types to reduce noise
  • No message history: if you disconnect and reconnect, you miss events that fired while you were away. The UI compensates by fetching fresh state on reconnect.

Testing with wscat

You don't need the frontend to test the WebSocket. Install wscat and connect directly:

bash
npm install -g wscat
wscat -c "ws://localhost:8003/ws/0x123456"

You should see a connection acknowledgment followed by periodic pings:

json
{"type":"connected","message":"Connected to Capital OS..."}
{"type":"ping","timestamp":"2026-04-05T12:00:00Z"}

If you trigger a proof generation in another terminal while connected, you'll see the proof_complete event arrive in real time.

Integration notes

If you're building against the WebSocket from outside the app:

  1. Always include a valid Starknet address in the URL path — the server uses it for event routing
  2. Handle reconnection yourself if you're not using the React hook
  3. Parse every message as JSON — there are no binary frames
  4. The alert event type includes a suggested action field, but it's advisory, not an instruction to execute

For the full API surface (REST endpoints, auth model, etc.), see API Reference.

Built by Obsqra Labs