Overview
StellarStack provides a WebSocket endpoint for real-time server updates and console streaming. The WebSocket connection requires authentication and supports subscribing to specific server updates.
Connection
Connect to the WebSocket endpoint:
ws://localhost:3001/api/ws
In production, use the secure WebSocket protocol:
wss://your-api-domain.com/api/ws
Authentication
WebSocket connections support two authentication methods:
1. Cookie-Based (Automatic)
If you have a valid Better Auth session cookie, authentication happens automatically on connection:
const ws = new WebSocket('ws://localhost:3001/api/ws');
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'auth_success') {
console.log('Authenticated as user:', message.userId);
}
};
2. Token-Based (Manual)
For environments where cookies don’t work (cross-origin), obtain a token first:
Step 1: Get WebSocket Token
curl http://localhost:3001/api/ws/token \
-H "Cookie: better-auth.session_token=<session_token>"
Response:
{
"token": "session_token_abc123",
"userId": "user_123"
}
Step 2: Authenticate WebSocket
const ws = new WebSocket('ws://localhost:3001/api/ws');
ws.onopen = () => {
// Send authentication message
ws.send(JSON.stringify({
type: 'auth',
token: 'session_token_abc123'
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'auth_success') {
console.log('Authenticated as user:', message.userId);
} else if (message.type === 'auth_error') {
console.error('Authentication failed:', message.error);
}
};
Message Types
Client to Server
Messages you can send to the server:
Subscribe to Server Updates
{
"type": "subscribe",
"serverId": "srv_abc123"
}
ID of the server to receive updates for
Response:
{
"type": "subscribed",
"serverId": "srv_abc123"
}
You must be authenticated to subscribe to server updates. Unauthenticated subscription attempts return an error.
Unsubscribe from Server Updates
{
"type": "unsubscribe",
"serverId": "srv_abc123"
}
ID of the server to stop receiving updates for
Response:
{
"type": "unsubscribed",
"serverId": "srv_abc123"
}
Ping
Keep the connection alive and check server responsiveness:
Response:
Server to Client
Messages you receive from the server:
Server Sync
Periodic server state updates (sent every 5 seconds for subscribed servers):
{
"type": "server:sync",
"serverId": "srv_abc123",
"data": {
"id": "srv_abc123",
"shortId": "abc123",
"name": "my-server",
"status": "running",
"suspended": false,
"containerId": "container_xyz",
"memory": 2048,
"disk": 10240,
"cpu": 100,
"cpuPinning": null,
"swap": 0,
"oomKillDisable": false,
"backupLimit": 5,
"nodeId": "node_456",
"blueprintId": "bp_789",
"ownerId": "user_123",
"createdAt": "2026-03-01T12:00:00.000Z",
"updatedAt": "2026-03-01T12:30:00.000Z",
"node": {
"id": "node_456",
"shortId": "node456",
"displayName": "US-East-1",
"host": "node1.example.com",
"port": 443,
"protocol": "https",
"sftpPort": 2022,
"isOnline": true,
"fqdn": "node1.example.com",
"daemonPort": 8080,
"location": {
"id": "loc_123",
"name": "US East"
}
},
"allocations": [
{
"id": "alloc_111",
"ip": "0.0.0.0",
"port": 25565,
"isPrimary": true
}
],
"blueprint": {
"id": "bp_789",
"name": "Minecraft Java"
}
}
}
ID of the server this update is for
Complete server state including node, allocations, and blueprint information
Authentication Success
{
"type": "auth_success",
"userId": "user_123"
}
Authentication Error
{
"type": "auth_error",
"error": "Invalid or expired session"
}
Error
{
"type": "error",
"error": "Authentication required to subscribe"
}
Complete Example
class StellarStackWebSocket {
constructor(apiUrl, sessionToken) {
this.apiUrl = apiUrl;
this.sessionToken = sessionToken;
this.ws = null;
this.authenticated = false;
this.subscriptions = new Set();
}
async connect() {
// Get WebSocket token
const response = await fetch(`${this.apiUrl}/api/ws/token`, {
headers: {
'Cookie': `better-auth.session_token=${this.sessionToken}`
}
});
const { token, userId } = await response.json();
// Connect to WebSocket
const wsUrl = this.apiUrl.replace('http', 'ws');
this.ws = new WebSocket(`${wsUrl}/api/ws`);
this.ws.onopen = () => {
console.log('WebSocket connected');
// Authenticate
this.ws.send(JSON.stringify({
type: 'auth',
token: token
}));
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
this.ws.onclose = () => {
console.log('WebSocket disconnected');
this.authenticated = false;
};
}
handleMessage(message) {
switch (message.type) {
case 'auth_success':
console.log('Authenticated as:', message.userId);
this.authenticated = true;
// Re-subscribe to previously subscribed servers
this.subscriptions.forEach(serverId => {
this.subscribe(serverId);
});
break;
case 'auth_error':
console.error('Auth error:', message.error);
break;
case 'subscribed':
console.log('Subscribed to server:', message.serverId);
break;
case 'server:sync':
console.log('Server update:', message.serverId, message.data);
this.onServerUpdate(message.serverId, message.data);
break;
case 'pong':
console.log('Pong received');
break;
}
}
subscribe(serverId) {
if (!this.authenticated) {
this.subscriptions.add(serverId);
return;
}
this.ws.send(JSON.stringify({
type: 'subscribe',
serverId: serverId
}));
this.subscriptions.add(serverId);
}
unsubscribe(serverId) {
this.ws.send(JSON.stringify({
type: 'unsubscribe',
serverId: serverId
}));
this.subscriptions.delete(serverId);
}
ping() {
this.ws.send(JSON.stringify({ type: 'ping' }));
}
onServerUpdate(serverId, data) {
// Override this method to handle server updates
console.log('Server update received:', serverId, data);
}
disconnect() {
if (this.ws) {
this.ws.close();
}
}
}
// Usage
const client = new StellarStackWebSocket(
'http://localhost:3001',
'your_session_token'
);
await client.connect();
// Subscribe to server updates
client.subscribe('srv_abc123');
// Handle updates
client.onServerUpdate = (serverId, data) => {
console.log(`Server ${serverId} status:`, data.status);
};
// Keep connection alive
setInterval(() => {
client.ping();
}, 30000); // Ping every 30 seconds
Update Frequency
Server sync updates are sent every 5 seconds for all subscribed servers. The periodic update system:
- Fetches server data in batch queries (one query for all subscribed servers)
- Only sends updates to authenticated, subscribed clients
- Includes complete server state with relationships (node, allocations, blueprint)
Connection Limits
There are no hard limits on WebSocket connections, but keep in mind:
- Each connection consumes server resources
- Subscribe only to servers you need updates for
- Unsubscribe when you no longer need updates
- Close connections when not in use
Error Handling
Handle WebSocket errors gracefully:
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
// Implement reconnection logic
if (event.code !== 1000) { // Not a normal closure
setTimeout(() => {
console.log('Reconnecting...');
connect();
}, 5000);
}
};
Best Practices
- Authenticate First - Always wait for
auth_success before subscribing
- Heartbeat - Send periodic ping messages to keep connection alive
- Reconnect - Implement automatic reconnection with exponential backoff
- Unsubscribe - Clean up subscriptions when components unmount
- Error Handling - Handle authentication failures and connection errors
- Resource Cleanup - Close WebSocket connections when not needed