Live News WebSocket
Real-time DeFi news lifecycle events. Sub-second push for approvals, sponsored placements, Tsunami arrivals, and editorial state changes. Stop polling — react when something actually changes.
wss://api.leviathannews.xyz/ws/news/
Quick start
Connect, watch news flow through. Works from any laptop with
pip install websockets.
import asyncio, json, websockets
async def listen():
async with websockets.connect(
'wss://api.leviathannews.xyz/ws/news/',
additional_headers={'Origin': 'https://leviathannews.xyz'}
) as ws:
while True:
msg = json.loads(await ws.recv())
if msg.get('type') == 'heartbeat':
continue
for ev in msg.get('events', []):
print(ev)
asyncio.run(listen())
Browser / JavaScript
const ws = new WebSocket('wss://api.leviathannews.xyz/ws/news/');
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'heartbeat') return;
for (const ev of (msg.events || [])) {
console.log(ev.type, ev.id, ev.headline);
// Refetch your cached REST query when something changes:
// queryClient.invalidateQueries(['live-news']);
}
};
Browsers automatically send Origin
based on the page that opened the connection. Origin must be a host
in our allowlist (e.g. leviathannews.xyz).
Event types
Anonymous connections receive only public events. Pre-publication events (submissions, schedules, kills, retracts) require staff JWT-cookie authentication.
| Type | Audience | Fires when |
|---|---|---|
news.approved |
Public | Article goes live (curated, vetted, broadcast). |
news.sponsored |
Public | Sponsored ad submitted, paid, or approved (paid tier, Squid Pass winner, or coupon-discounted). Includes sponsored block with tier info. |
news.tsunami |
Public | Tsunami item ingested from upstream wire (unverified firehose). |
news.tsunami_promoted |
Public | Tsunami item promoted to editorial. |
news.submitted |
Staff | New submission entered the editorial queue, or scheduled article reverted to submitted. |
news.scheduled |
Staff | Article approved with a future publish_at. |
news.killed |
Staff | Article editorially killed. |
news.retracted |
Staff | Approved article retracted. |
Frame shape
Server frames arrive as either an events batch or a heartbeat. Events are coalesced into 200ms windows, up to 10 per frame.
Event batch
{
"events": [
{
"type": "news.approved",
"id": 239046,
"slug": "agentic-commerce-reaches-first-schelling-point",
"status": "approved",
"origin": "telegram",
"headline": "Agentic commerce reaches first schelling point...",
"date_posted": "2026-05-07T20:56:43.208982+00:00",
"previous_status": "submitted"
}
],
"t": 1746649003
}
Sponsored event (with sponsored block)
{
"events": [{
"type": "news.sponsored",
"id": 239500,
"slug": "sponsor-launches-mainnet",
"status": "approved",
"origin": "x402_ad",
"headline": "Sponsor launches mainnet...",
"date_posted": "2026-05-07T21:30:00+00:00",
"sponsored": {
"tier": "priority", // "standard" | "priority" | "express" | "squid_pass"
"ad_status": "sponsored_approved",
"is_paid": true,
"coupon_applied": false,
"priority": "high"
}
}],
"t": 1746651000
}
Heartbeat
{"type": "heartbeat"}
Sent every ~25 seconds to keep the connection alive through proxies and load balancers. Safe to ignore.
Recommended pattern
Payloads are intentionally thin (id + transition metadata). Treat the WebSocket as a change notification — refetch the cached REST endpoint ye care about when an event arrives.
- Open the WebSocket on app boot. Subscribe.
-
On any event, invalidate / refetch the REST list ye're showing
— typically
/api/v1/news/?sort_type=new. Cache invalidation runs server-side before the event fires, so a refetch within ~5 seconds will hit fresh data. - If the WebSocket disconnects, fall back to polling the same REST endpoint every 60s. Reconnect with exponential backoff in the background.
-
Filter
news.sponsoredevents bysponsored.tierif ye care about specific tiers (Squid Pass winners gettier=squid_pass).
Reconnect with backoff + heartbeat watchdog
Spelling this out costs ~20 lines and saves a class of bug reports. Exponential backoff with jitter prevents a thundering herd against our endpoint after a network blip, and the heartbeat watchdog catches silent connection deaths the server-side close events can't surface (proxy idle-out, OS sleep, etc.):
// Exponential backoff with jitter + heartbeat watchdog.
let attempt = 0, lastBeat = Date.now(), ws;
function connect() {
ws = new WebSocket('wss://api.leviathannews.xyz/ws/news/');
ws.onopen = () => { attempt = 0; lastBeat = Date.now(); };
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'heartbeat') { lastBeat = Date.now(); return; }
if (msg.type === 'reconcile') { /* refetch REST */ return; }
for (const ev of (msg.events || [])) handleEvent(ev);
};
ws.onclose = () => {
const wait = Math.min(30000, 500 * 2 ** attempt) + Math.random() * 500;
attempt += 1;
setTimeout(connect, wait);
};
}
setInterval(() => {
if (Date.now() - lastBeat > 60000 && ws) ws.close(); // forces reconnect
}, 10000);
connect();
Connection rules
-
Origin required. Connect with
Origin: https://leviathannews.xyzor another host in our allowlist. Browsers send this automatically. - Read-only. Client messages are ignored. Don't send anything; just listen.
- Connection caps. 500 concurrent connections globally, plus 5 concurrent connections per egress IP. The per-IP counter decrements when your connection closes, so a short-lived reconnect cycle from one IP is fine. Slots that leak past disconnect (network drop, proxy crash) self-heal within ~60s. If you run multiple replicas behind a single egress IP and need more than 5 concurrent slots, contact us — we'll lift the cap on a case-by-case basis or you can put each replica on a distinct egress.
-
Backpressure. If the server falls behind on a
slow client, it drops the oldest queued event and emits a
{"type": "reconcile", "reason": "backlog"}frame. On reconcile, refetch the REST list to recover. - Auto-close on idle. No idle timeout from the server, but heartbeats every 25s let proxies keep the connection alive. Implement client-side heartbeat-watchdog (no heartbeat > 60s = reconnect).
-
Failure modes worth knowing about.
- Connect attempt closes immediately,
no upgrade. Most likely your
Originheader is missing or not in our allowlist. SetOrigin: https://leviathannews.xyzon the handshake; browsers do this automatically. - Connection closes within ~1 second of a successful upgrade, with no event frames. The feature is paused (the server-side flag is off, or the runtime kill-switch is engaged). Wait and reconnect with backoff; we'll be back when ops releases.
- Connect attempt fails with
HTTP 403during the upgrade handshake. Connection cap exceeded — either global (500 concurrent) or your egress IP hit its cap (5 concurrent). The consumer rejects with WebSocket close code4003, but because the rejection happens before the handshake completes, the Apache layer surfaces it as an HTTP 403 to client libraries (typicallyInvalidStatusor similar in client exception language). Reconnect with backoff, or contact us if you legitimately need more slots.
- Connect attempt closes immediately,
no upgrade. Most likely your
Authentication
Public events are anonymous. To receive pre-publication events
(submitted,
scheduled,
killed,
retracted), ye need a staff
JWT cookie set by the standard auth flow.
The browser sends the JWT cookie automatically on the WebSocket upgrade handshake; no extra client code required. The consumer inspects it server-side and filters events accordingly.
There is currently no API-key authentication for the WebSocket. Browser cookie-JWT only for staff-gated events.
FAQ
Why not just send the full article in the WebSocket frame?
The REST endpoint already returns the canonical, fully-formatted article shape. Duplicating that in the WebSocket payload would invite drift. Thin events keep the contract narrow and let ye reuse yer existing REST cache.
How fast is "real-time"?
Sub-second from publish to client. The publisher fires after the DB transaction commits and after the REST cache is invalidated, so a refetch on event receipt always sees fresh data.
What happens during a Tsunami storm (hundreds of events/min)?
Events coalesce into 200ms batches with up to 10 events per frame.
Sponsored events fast-path in their own frame ahead of the queue.
Slow clients hit the backlog limit (100 events) and receive a
reconcile frame to recover via REST.
Is there a sandbox / test mode?
No, the endpoint streams real production events — no
sandbox. The Tsunami feed (
type: news.tsunami)
arrives every 1-2 minutes during US/EU business hours, so
a connect-now client testing reconnect logic should see
real frames within ~5 minutes. Outside business hours
you'll need to wait longer or test against your own
fixtures.
What if the WebSocket goes down?
Fall back to polling
/api/v1/news/?sort_type=new
every 60s. The server-side cache TTL is 60s, so ye're never more
than a minute behind.
Need the REST API?
Full reference for the existing endpoints — articles, tags, tokens, leaderboards, governance.
REST API Docs →