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:
| Environment | URL |
|---|---|
| Dev | ws://localhost:8003/ws/{user_address} |
| Prod | wss://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:
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
| Event | When it fires | Key fields |
|---|---|---|
strategy_update | A strategy is created, modified, or scored | strategy_id, pool_id, apy, risk_score |
market_change | Significant price or rate movement in a tracked pool | change_type, pool_id, old_value, new_value, change_pct |
alert | A position crosses a threshold (drawdown, exposure, health factor) | severity, alert_type, message, action |
proof_complete | A proof finishes generating and is ready for verification | proof_type, proof_hash, success |
position_update | A position's value changes (from market movement or execution) | position_id, current_value |
agent_status_change | An agent starts, stops, pauses, errors, or resumes | agent_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:
npm install -g wscat
wscat -c "ws://localhost:8003/ws/0x123456"You should see a connection acknowledgment followed by periodic pings:
{"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:
- Always include a valid Starknet address in the URL path — the server uses it for event routing
- Handle reconnection yourself if you're not using the React hook
- Parse every message as JSON — there are no binary frames
- The
alertevent type includes a suggestedactionfield, but it's advisory, not an instruction to execute
For the full API surface (REST endpoints, auth model, etc.), see API Reference.