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
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 |
- 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.