WebSocket API Documentation

Real-time stock news feed with AI sentiment analysis. Connect via WebSocket to receive live updates.

Authentication

All WebSocket connections require authentication using a bearer token. Get your token from the Account page after subscribing to a Pro plan.

wss://dev.stocknews.live/ws?token=YOUR_AUTH_TOKEN

WebSocket Endpoint

WS wss://dev.stocknews.live/ws?token={token}

Establishes a WebSocket connection for real-time news updates. The connection will be closed if your subscription expires.

Message Types

Connected Message

Sent immediately after successful authentication:

{
  "type": "connected",
  "message": "WebSocket connection established",
  "subscription": {
    "expiresAt": "2026-03-06T00:00:00.000Z",
    "isActive": true
  }
}

News Message

Sent whenever new stock news is detected and analyzed:

{
  "type": "news",
  "timestamp": "2026-02-06T12:34:56.789Z",
  "data": {
    "ticker": "AAPL",
    "sentimentScore": 75,
    "sentimentLabel": "Bullish",
    "confidence": 92,
    "summary": "Apple announces record quarterly earnings...",
    "content": "Full article content...",
    "category": "earnings",
    "stockTickers": ["AAPL"],
    "keyFactors": ["earnings beat", "revenue growth"],
    "isStockRelated": true,
    "articleUrl": "https://stocknews.live/news/AAPL/article-slug",
    "articleSlug": "article-slug",
    "channel": "Stock News",
    "guild": "Trading Community",
    "timestamp": "2026-02-06T12:34:56.789Z",
    "price": 175.50,
    "priceChange": 2.5,
    "priceCurrency": "USD",
    "priceFormatted": "$175.50 (+2.5%)",
    "volume": 45000000,
    "marketCap": "2.8T",
    "sector": "Technology"
  }
}

Client Messages

You can send these messages to the server:

Type Description Payload
ping Keep-alive ping {"type": "ping"}
subscribe Subscribe to specific tickers (optional) {"type": "subscribe", "tickers": ["AAPL", "TSLA"]}

Integration Examples

const WebSocket = require('ws');

const token = 'YOUR_AUTH_TOKEN';
const ws = new WebSocket(`wss://dev.stocknews.live/ws?token=${token}`);

ws.on('open', () => {
    console.log('Connected to StockNews WebSocket');
});

ws.on('message', (data) => {
    const message = JSON.parse(data);
    
    if (message.type === 'connected') {
        console.log('Authenticated:', message.subscription);
    } else if (message.type === 'news') {
        const news = message.data;
        console.log(`News for ${news.ticker}:`, news.sentimentLabel);
        console.log(`Sentiment Score: ${news.sentimentScore}/100`);
        console.log('Summary:', news.summary);
        console.log('Price:', news.priceFormatted);
        console.log('Timestamp:', news.timestamp);
        
        // Process the news data
        handleNewsUpdate(news);
    }
});

ws.on('error', (error) => {
    console.error('WebSocket error:', error);
});

ws.on('close', () => {
    console.log('Connection closed');
});

function handleNewsUpdate(news) {
    // Your custom logic here
    if (news.sentimentScore >= 20) {
        console.log('Bullish signal detected!');
    } else if (news.sentimentScore <= -20) {
        console.log('Bearish signal detected!');
    }
}
import websocket
import json

token = 'YOUR_AUTH_TOKEN'
ws_url = f'wss://dev.stocknews.live/ws?token={token}'

def on_message(ws, message):
    data = json.loads(message)
    
    if data['type'] == 'connected':
        print('Authenticated:', data['subscription'])
    elif data['type'] == 'news':
        news = data['data']
        print(f"News for {news['ticker']}: {news['sentimentLabel']}")
        print(f"Sentiment Score: {news['sentimentScore']}/100")
        print(f"Summary: {news['summary']}")
        print(f"Price: {news['priceFormatted']}")
        print(f"Timestamp: {news['timestamp']}")
        
        # Process the news data
        handle_news_update(news)

def on_error(ws, error):
    print('WebSocket error:', error)

def on_close(ws):
    print('Connection closed')

def on_open(ws):
    print('Connected to StockNews WebSocket')

def handle_news_update(news):
    # Your custom logic here
    if news['sentimentScore'] >= 20:
        print('Bullish signal detected!')
    elif news['sentimentScore'] <= -20:
        print('Bearish signal detected!')

ws = websocket.WebSocketApp(
    ws_url,
    on_message=on_message,
    on_error=on_error,
    on_close=on_close,
    on_open=on_open
)

ws.run_forever()
<?php
use Ratchet\Client\WebSocket;
use React\EventLoop\Factory;

require __DIR__ . '/vendor/autoload.php';

$token = 'YOUR_AUTH_TOKEN';
$wsUrl = "wss://dev.stocknews.live/ws?token={$token}";

$loop = Factory::create();

\Ratchet\Client\connect($wsUrl, [], [], $loop)
    ->then(function(WebSocket $conn) {
        echo "Connected to StockNews WebSocket\n";
        
        $conn->on('message', function($msg) use ($conn) {
            $data = json_decode($msg, true);
            
            if ($data['type'] === 'connected') {
                echo "Authenticated: " . json_encode($data['subscription']) . "\n";
            } elseif ($data['type'] === 'news') {
                $news = $data['data'];
                echo "News for {$news['ticker']}: {$news['sentimentLabel']}\n";
                echo "Sentiment Score: {$news['sentimentScore']}/100\n";
                echo "Summary: {$news['summary']}\n";
                echo "Price: {$news['priceFormatted']}\n";
                echo "Timestamp: {$news['timestamp']}\n";
                
                // Process the news data
                handleNewsUpdate($news);
            }
        });
        
        $conn->on('close', function($code = null, $reason = null) {
            echo "Connection closed ({$code} - {$reason})\n";
        });
    }, function(\Exception $e) {
        echo "Could not connect: {$e->getMessage()}\n";
    });

function handleNewsUpdate($news) {
    // Your custom logic here
    if ($news['sentimentScore'] >= 20) {
        echo "Bullish signal detected!\n";
    } elseif ($news['sentimentScore'] <= -20) {
        echo "Bearish signal detected!\n";
    }
}

$loop->run();
?>
const token = 'YOUR_AUTH_TOKEN';
const ws = new WebSocket(`wss://dev.stocknews.live/ws?token=${token}`);

ws.onopen = () => {
    console.log('Connected to StockNews WebSocket');
};

ws.onmessage = (event) => {
    const message = JSON.parse(event.data);
    
    if (message.type === 'connected') {
        console.log('Authenticated:', message.subscription);
    } else if (message.type === 'news') {
        const news = message.data;
        console.log(`News for ${news.ticker}:`, news.sentimentLabel);
        console.log(`Sentiment Score: ${news.sentimentScore}/100`);
        console.log('Summary:', news.summary);
        console.log('Price:', news.priceFormatted);
        console.log('Timestamp:', news.timestamp);
        
        // Update UI or process data
        displayNews(news);
    }
};

ws.onerror = (error) => {
    console.error('WebSocket error:', error);
};

ws.onclose = () => {
    console.log('Connection closed');
    // Optionally reconnect
    setTimeout(() => {
        connectWebSocket();
    }, 5000);
};

function displayNews(news) {
    // Update your UI with the news data
    const newsElement = document.createElement('div');
    const timestamp = new Date(news.timestamp).toLocaleString();
    newsElement.innerHTML = `
        <h3>${news.ticker} - ${news.sentimentLabel} (${news.sentimentScore}/100)</h3>
        <p>${news.summary}</p>
        <p>Price: ${news.priceFormatted || 'N/A'}</p>
        <p>Time: ${timestamp}</p>
        ${news.articleUrl ? `<a href="${news.articleUrl}" target="_blank">Read Article</a>` : ''}
    `;
    document.getElementById('news-feed').appendChild(newsElement);
}

Data Fields

Field Type Description
ticker string Primary stock ticker symbol (e.g., "AAPL")
sentimentScore number Sentiment score from -100 (very bearish) to +100 (very bullish)
sentimentLabel string Human-readable label: "Very Bearish", "Bearish", "Neutral", "Bullish", "Very Bullish"
confidence number AI confidence score (0-100)
summary string Brief summary of the news
content string Full article content
category string News category (e.g., "earnings", "merger", "product")
stockTickers array All stock tickers mentioned in the article
keyFactors array Key factors affecting sentiment
price number Current stock price
priceChange number Price change percentage
priceFormatted string Formatted price display (e.g., "$175.50 (+2.5%)")
volume number Trading volume
marketCap string Market capitalization
sector string Stock sector
articleUrl string URL to full article
timestamp string ISO 8601 timestamp

Sample Response

{
  "type": "news",
  "timestamp": "2026-02-06T12:34:56.789Z",
  "data": {
    "ticker": "AAPL",
    "sentimentScore": 75,
    "sentimentLabel": "Bullish",
    "confidence": 92,
    "summary": "Apple Inc. reported record quarterly earnings, beating analyst expectations...",
    "content": "Full article content here...",
    "category": "earnings",
    "stockTickers": ["AAPL"],
    "keyFactors": ["earnings beat", "revenue growth", "strong iPhone sales"],
    "isStockRelated": true,
    "articleUrl": "https://stocknews.live/news/AAPL/apple-record-earnings-q1-2026",
    "articleSlug": "apple-record-earnings-q1-2026",
    "channel": "Stock News",
    "guild": "Trading Community",
    "timestamp": "2026-02-06T12:34:56.789Z",
    "price": 175.50,
    "priceChange": 2.5,
    "priceCurrency": "USD",
    "priceFormatted": "$175.50 (+2.5%)",
    "volume": 45000000,
    "marketCap": "2.8T",
    "sector": "Technology"
  }
}

Error Handling

The WebSocket connection may close with these codes:

  • 1008: Authentication failed or subscription expired. The close reason will contain a JSON object with error details:
    {
      "error": "Invalid token" | "Subscription expired",
      "message": "Human-readable error message",
      "expiresAt": "2026-03-06T00:00:00.000Z" // if subscription expired
    }
  • 1000: Normal closure

Always implement reconnection logic with exponential backoff for production applications. Do not auto-reconnect on authentication/subscription errors (code 1008).

ws.onclose = (event) => {
    if (event.reason) {
        try {
            const errorData = JSON.parse(event.reason);
            if (errorData.error === 'Invalid token' || errorData.error === 'Subscription expired') {
                console.error('Authentication error:', errorData.message);
                // Don't reconnect - user needs to fix their token/subscription
                return;
            }
        } catch (e) {
            // Not JSON, handle as regular close
        }
    }
    // Reconnect for other errors
    setTimeout(() => connectWebSocket(), 5000);
};

Rate Limits

Important: Rate limits apply only to client input messages (messages you send to the server). Server broadcasts (news updates) are NOT rate limited - you will receive all news updates in real-time without restrictions.

To prevent abuse and ensure fair usage, the following rate limits are enforced on client inputs:

Limit Type Limit Window Applies To
Connection Attempts 10 attempts Per IP per minute Initial connections
Concurrent Connections 5 connections Per user Active connections
Ping Messages 20 messages Per connection per minute Client → Server
Subscribe Messages 10 messages Per connection per minute Client → Server
Total Messages 30 messages Per connection per minute Client → Server
✅ No Rate Limits On:
  • News broadcasts (Server → Client)
  • Connected messages (Server → Client)
  • Error messages (Server → Client)
  • Any server-to-client communication

You will receive all news updates in real-time as long as your subscription is active.

Rate Limit Exceeded: If you exceed any rate limit on client inputs, the server will send an error message and may close the connection. The error message will include details about which limit was exceeded.

{
  "type": "error",
  "error": "Rate limit exceeded",
  "message": "Too many ping messages. Please slow down."
}

Best Practices:

  • Implement exponential backoff when reconnecting
  • Don't send ping messages more frequently than once every 3 seconds
  • Cache subscription preferences instead of resubscribing frequently
  • Handle rate limit errors gracefully in your application
  • You don't need to worry about receiving too many news updates - all broadcasts are unlimited

Support

For questions or issues, please contact support or visit your Account page.