Browser WebSocket
Connect to the Finlight.me WebSocket API directly from a web browser using the native WebSocket API. This is ideal for dashboards, trading terminals, and any frontend application that needs real-time article updates without a server-side proxy.
The browser WebSocket API does not support custom headers. Authentication is handled via query parameters instead of the x-api-key header. Your API key will be visible in the WebSocket URL — only use this approach in environments where that is acceptable (e.g., authenticated dashboards). Never expose your API key in public-facing client-side code.
How It Works
- Connect to
wss://wss.finlight.me(enhanced) orwss://wss.finlight.me/raw(raw) with your API key as a query parameter - Subscribe by sending a JSON message with your filter criteria and a
clientNonce - Receive an
admitmessage confirming your connection, followed bysendArticlemessages as articles arrive - Keep alive by sending
pingmessages every 25 seconds - Disconnect by calling
ws.close()
Connection URLs
- Name
Enhanced- Type
- wss://wss.finlight.me
- Description
Articles with full enrichment: sentiment analysis, entity extraction, categories, and full content (based on your subscription tier).
- Name
Raw- Type
- wss://wss.finlight.me/raw
- Description
Low-latency raw articles without enrichment. Best for speed-first use cases where you need articles as fast as possible.
Query Parameters
- Name
apiKey- Type
- string
- Description
Your Finlight API key. Used for authentication since browser WebSocket cannot set custom headers.
- Name
takeover- Type
- string
- Description
Set to
"true"to automatically replace the oldest connection when your concurrent connection limit is reached.
- Name
clientVersion- Type
- string
- Description
Optional identifier for your client application. E.g.
"my-dashboard/1.0"
Subscribe Message
After the connection opens, send a JSON message to subscribe to articles. The server will respond with an admit message, then begin pushing matching articles.
- Name
clientNonce- Type
- string
- Description
A unique identifier (e.g., UUID) for this subscription request. The server echoes it back in the
admitresponse.
- Name
query- Type
- string
- Description
Search query to find relevant articles. Supports advanced queries.
- Name
sources- Type
- string[]
- Description
Filter by source domains. E.g.
["www.reuters.com", "www.cnbc.com"]
- Name
excludeSources- Type
- string[]
- Description
Exclude specific source domains.
- Name
tickers- Type
- string[]
- Description
Filter by stock ticker symbols. E.g.
["AAPL", "NVDA"](Enhanced only)
- Name
countries- Type
- string[]
- Description
Filter by country codes in ISO 3166-1 alpha-2. E.g.
["US", "DE"](Enhanced only)
- Name
language- Type
- string
- Description
Filter by language in ISO 639-1. E.g.
"en"
Server Messages
The server sends JSON messages with an action field indicating the message type:
- Name
admit- Type
- object
- Description
Sent after a successful handshake. Contains
leaseId,serverNow(timestamp), and yourclientNonce.
- Name
sendArticle- Type
- object
- Description
A new article matching your subscription. The article data is in the
datafield.
- Name
pong- Type
- object
- Description
Response to your
pingheartbeat.
- Name
preempted- Type
- object
- Description
Your connection was replaced by another session (when another client connected with
takeover: true).
- Name
error- Type
- object
- Description
An error occurred. Check the
dataorerrorfield for details.
Complete Example
A minimal, framework-agnostic browser WebSocket client with automatic reconnection and heartbeat:
- Authenticates via query parameters
- Sends a subscribe message on connect
- Maintains a 25-second heartbeat interval
- Reconnects with exponential backoff (500ms to 10s)
- Handles all server message types
Browser Client
const API_KEY = 'YOUR_API_KEY'
const WSS_URL = 'wss://wss.finlight.me' // Use '/raw' path for raw articles
let ws = null
let pingInterval = null
let reconnectTimeout = null
let reconnectAttempt = 0
function connect(filters = {}) {
const params = new URLSearchParams({
apiKey: API_KEY,
takeover: 'true',
clientVersion: 'my-app/1.0',
})
const url = `${WSS_URL}?${params}`
ws = new WebSocket(url)
ws.onopen = () => {
console.log('Connected')
reconnectAttempt = 0
// Send subscription with filters
ws.send(JSON.stringify({
clientNonce: crypto.randomUUID(),
query: filters.query || '',
language: filters.language || 'en',
sources: filters.sources || [],
tickers: filters.tickers || [],
countries: filters.countries || [],
}))
// Start heartbeat
pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
action: 'ping',
t: Date.now(),
}))
}
}, 25000)
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
switch (msg.action) {
case 'admit':
console.log('Admitted, lease:', msg.leaseId)
break
case 'sendArticle':
console.log('Article:', msg.data.title)
// Handle the article here
break
case 'pong':
break // Heartbeat OK
case 'preempted':
console.warn('Connection replaced')
break
case 'error':
console.error('Server error:', msg.data || msg.error)
break
}
}
ws.onclose = (event) => {
clearInterval(pingInterval)
// Don't reconnect on policy violations
if (event.code === 1008 || event.code === 4002) {
console.error('Connection blocked')
return
}
// Exponential backoff reconnect
const delay = Math.min(500 * 2 ** reconnectAttempt, 10000)
reconnectAttempt++
console.log(`Reconnecting in ${delay}ms...`)
reconnectTimeout = setTimeout(() => connect(filters), delay)
}
ws.onerror = () => console.error('WebSocket error')
}
function disconnect() {
clearInterval(pingInterval)
clearTimeout(reconnectTimeout)
if (ws) ws.close(1000)
}
// Usage
connect({ query: 'Nvidia', language: 'en', countries: ['US'] })
admit Response
{
"action": "admit",
"leaseId": "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
"serverNow": 1708185600000,
"clientNonce": "your-uuid-here"
}
sendArticle Response
{
"action": "sendArticle",
"data": {
"link": "https://www.reuters.com/technology/nvidia-2026-02-17",
"source": "www.reuters.com",
"title": "Nvidia Reports Record Revenue",
"summary": "Nvidia announced record quarterly revenue...",
"publishDate": "2026-02-17T10:30:00Z",
"language": "en",
"sentiment": "positive",
"confidence": 0.92,
"countries": ["US"],
"categories": ["markets", "technology"],
"companies": [
{
"name": "NVIDIA Corporation",
"ticker": "NVDA",
"country": "US"
}
]
}
}
Enhanced vs Raw
| Feature | Enhanced (/) | Raw (/raw) |
|---|---|---|
| Sentiment analysis | Yes | No |
| Entity extraction (companies) | Yes (tier-dependent) | No |
| Categories | Yes | No |
| Full content | Yes (tier-dependent) | No |
| Latency | Standard | Lowest |
Filter: tickers | Yes | No |
Filter: countries | Yes | No |
Filter: query, sources, language | Yes | Yes |
Raw WebSocket subscriptions only support query, sources, excludeSources, and language filters. Ticker and country filters are silently ignored on the raw endpoint.
What's Next?
- Review the Enhanced Subscribe page for the full article model and all available fields
- Check Raw Subscribe for raw-specific details
- Learn about Advanced Query Building to refine your filters
- Use the REST API for historical article lookups alongside your real-time stream