WebSocket API
The Dualis Finance WebSocket API provides real-time data streams for prices, pool updates, position changes, liquidation events, governance activity, and user notifications. The endpoint is powered by Fastify's built-in WebSocket support.
Endpoint
GET ws://localhost:4000/v1/wsIn production, use the secure variant: wss://api.dualis.finance/v1/ws. The server accepts standard WebSocket upgrade requests.
Authentication
After establishing the WebSocket connection, the client must send an authentication message within 30 seconds. Connections that fail to authenticate within this window are terminated automatically.
{
"type": "auth",
"token": "your-jwt-token"
}On successful authentication, the server responds with:
{
"type": "auth",
"status": "ok",
"userId": "usr_abc123"
}Subscribing to Channels
Once authenticated, subscribe to one or more data channels. Each subscription message specifies a channel name and optional filter parameters:
{
"type": "subscribe",
"channel": "prices",
"params": {
"assets": ["ETH", "BTC", "USDC"]
}
}To unsubscribe from a channel:
{
"type": "unsubscribe",
"channel": "prices"
}Available Channels
| Channel | Description | Update Frequency |
|---|---|---|
prices | Real-time asset price updates from oracle feeds | ~1s per asset |
pools | Pool state changes: utilization, rates, TVL | On change (typically every block) |
positions | User position updates: supply balances, borrow balances, health factors | On change |
liquidations | Liquidation events: trigger, auction start, settlement | On event |
governance | Proposal creation, voting updates, execution results | On event |
notifications | User-specific alerts: health factor warnings, reward claims, system notices | On event |
Message Format
All server-sent messages follow a consistent envelope structure:
{
"type": "data",
"channel": "prices",
"timestamp": "2026-02-28T10:30:00.000Z",
"data": {
"asset": "ETH",
"price": "3245.67",
"change24h": -0.0182,
"source": "oracle_aggregated"
}
}Keepalive
The server sends WebSocket ping frames every 30 seconds. Clients must respond with pong frames (most WebSocket libraries handle this automatically). If the server receives no pong within 30 seconds, it considers the connection dead and closes it.
Clients can also send application-level heartbeat messages:
{
"type": "ping"
}The server responds with:
{
"type": "pong",
"timestamp": "2026-02-28T10:30:15.000Z"
}Client Example
A complete TypeScript client that connects, authenticates, subscribes to price updates, and handles reconnection:
const WS_URL = 'ws://localhost:4000/v1/ws';
const AUTH_TOKEN = 'your-jwt-token';
let ws: WebSocket;
let reconnectTimer: ReturnType<typeof setTimeout>;
function connect() {
ws = new WebSocket(WS_URL);
ws.onopen = () => {
// Authenticate within 30s window
ws.send(JSON.stringify({ type: 'auth', token: AUTH_TOKEN }));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'auth':
if (msg.status === 'ok') {
// Subscribe to channels after auth
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'prices',
params: { assets: ['ETH', 'BTC'] },
}));
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'positions',
}));
}
break;
case 'data':
console.log(`[${msg.channel}]`, msg.data);
break;
case 'error':
console.error('WS error:', msg.message);
break;
}
};
ws.onclose = (event) => {
console.log(`Connection closed: ${event.code}`);
// Reconnect after 3 seconds
reconnectTimer = setTimeout(connect, 3000);
};
ws.onerror = () => {
ws.close();
};
}
connect();Error Messages
The server sends error messages for invalid requests or server-side issues:
| Close Code | Reason |
|---|---|
| 4001 | Authentication timeout (30s exceeded) |
| 4002 | Invalid or expired JWT token |
| 4003 | Unknown channel name |
| 4004 | Rate limit exceeded |
| 1011 | Internal server error |