Code Playbook · Python & JavaScript

Stream Real-Time Whale Swaps with WebSockets

Polling a whale-activity endpoint on an interval has two problems: you miss whatever happens between polls, and you burn through your daily call budget checking for events that aren’t there yet. A WebSocket push feed solves both — the server sends you a swap event the moment it happens, and nothing when it doesn’t. This playbook covers the real-time whale swap feed: large on-chain DEX swaps detected on BSC and Avalanche, pushed to you as they occur.

This is a correction worth stating up front: this API does not have a WebSocket for derivatives/funding data. The one real-time WebSocket endpoint streams on-chain whale DEX swap events — large swaps on BSC and Avalanche detected in real time. That’s still a genuinely useful feed for a live alert bot or a dashboard ticker, just not derivatives data, so this playbook covers what the socket actually sends.

The problem: polling misses events

If you poll a whale-events endpoint every 30 seconds, you only ever see a snapshot — two swaps that both happened in that window look identical to one, and a big swap that lands right after your poll waits up to 30 seconds before you know about it. For a live alert bot or a dashboard ticker, that lag and that call overhead both matter. A persistent WebSocket connection removes both problems: the server pushes each qualifying swap the instant it’s detected, and idle time costs you nothing.

This is the same tradeoff every real-time integration eventually runs into: REST polling is simple to write but scales badly with how often you actually need fresh data, while a persistent socket connection is a bit more code up front in exchange for genuinely real-time delivery. For a feed like whale swaps — where individual events are what you care about, not periodic snapshots — the socket is the better fit, and this playbook covers the full connection lifecycle so you don’t have to reverse-engineer it from trial and error.

Prerequisites

This socket requires a paid plan. Opening the live swap WebSocket itself needs a Trader, Pro, or Enterprise tier key. A free-tier key can still mint a ticket, but the socket connection is rejected with an HTTP 402. If you just need a read-only feed for public display, skip straight to the free Server-Sent-Events fallback in “What to build next” below — no key, no ticket, no tier required.

The ticket-then-connect flow

You can’t put a long-lived API key directly in a wss:// URL — browsers can’t set custom headers on a WebSocket handshake, so the key would end up sitting in server logs and browser history. Instead, the flow is a two-step handshake:

  1. POST your API key to /v1/ws/ticket and get back a single-use ticket good for 60 seconds.
  2. Open the WebSocket with that ticket in the query string, before it expires.

Step 1 — mint a ticket

bash
POST https://api.smartmoneyapi.com/v1/ws/ticket Header: X-API-Key: <your key>
json
{"ticket": "kx7...43-char-url-safe-token...9zQ", "expires_in": 60}

Step 2 — connect

text
wss://api.smartmoneyapi.com/v1/ws/live-swaps?ticket=<ticket>

Only Trader tier and above may actually open this socket. A free-tier ticket will connect the handshake but the server immediately responds:

json
{"error": "payment_required", "message": "Live swap WebSocket is a paid feature. Upgrade to Trader or Pro at smartmoneyapi.com/pricing. The same stream is available free for browser display via the public SSE endpoint /v1/stream/public-swaps."}

Python: mint a ticket and stream

This script mints a ticket, connects with the websockets library, and prints every swap event above a configurable USD threshold. It mints a fresh ticket and reconnects automatically if the connection drops, since each ticket is single-use.

python
import os import json import asyncio import requests import websockets API_KEY = os.environ["SMARTMONEY_API_KEY"] BASE_URL = "https://api.smartmoneyapi.com" MIN_USD_TO_PRINT = 2000 # only print swaps at or above this notional def mint_ticket(): """Tickets are single-use and expire in 60s -- mint one right before connecting.""" resp = requests.post( f"{BASE_URL}/v1/ws/ticket", headers={"X-API-Key": API_KEY}, timeout=10, ) resp.raise_for_status() return resp.json()["ticket"] # {"ticket": "...", "expires_in": 60} async def stream_whale_swaps(): while True: ticket = mint_ticket() uri = f"wss://api.smartmoneyapi.com/v1/ws/live-swaps?ticket={ticket}" try: async with websockets.connect(uri) as ws: async for raw in ws: frame = json.loads(raw) ftype = frame.get("type") if ftype == "hello": print(f"connected: tier={frame['tier']} min_usd={frame['min_usd']} " f"chains={frame['chains']}") elif ftype == "heartbeat": continue # keepalive only, sent roughly every 20s of inactivity elif ftype == "swap": if frame["amount_usd"] >= MIN_USD_TO_PRINT: who = frame["swapper_label"] or frame["swapper_short"] print(f"[{frame['chain']}] {frame['pair']} " f"${frame['amount_usd']:,.0f} on {frame['dex']} ({who}) " f"{frame['explorer_url']}") except websockets.exceptions.ConnectionClosed as exc: print(f"disconnected ({exc}); reconnecting with a fresh ticket in 2s") await asyncio.sleep(2) except Exception as exc: # A free/pro-but-not-Trader key gets rejected with HTTP 402 at the # handshake -- this feed is Trader tier and above only. print(f"connection failed: {exc}") print("If this is a 402, this stream needs a Trader-tier key -- see /pricing") await asyncio.sleep(5) if __name__ == "__main__": asyncio.run(stream_whale_swaps())
This feed needs a Trader-tier key

The ticket-then-connect flow above only completes for Trader, Pro, and Enterprise accounts. $29/mo gets you the live socket plus all symbols and 3000 calls/day on the REST API.

See pricing →

JavaScript: browser WebSocket variant

The same two-step flow works from a browser using the native WebSocket object. One caveat: browsers don’t expose the HTTP status code of a failed WebSocket handshake, so a 402 from an under-tier key just looks like the socket closing without ever sending a hello frame — the code below notes this.

html
<script> async function connectWhaleFeed(apiKey) { // Minting a ticket only needs a valid key of any tier. const ticketRes = await fetch("https://api.smartmoneyapi.com/v1/ws/ticket", { method: "POST", headers: { "X-API-Key": apiKey }, }); const { ticket } = await ticketRes.json(); const ws = new WebSocket(`wss://api.smartmoneyapi.com/v1/ws/live-swaps?ticket=${ticket}`); ws.onopen = () => console.log("socket open, waiting for hello frame..."); ws.onmessage = (event) => { const frame = JSON.parse(event.data); switch (frame.type) { case "hello": console.log("connected:", frame); break; case "heartbeat": break; // keepalive only case "swap": if (frame.amount_usd >= 2000) { const who = frame.swapper_label || frame.swapper_short; console.log(`${frame.chain} ${frame.pair} $${frame.amount_usd.toLocaleString()} (${who})`); } break; } }; // Browsers don't expose the HTTP status of a failed WebSocket handshake -- // if the key isn't Trader-tier or above, the socket just closes without ever // sending a "hello" frame. Use the Python example above if you need to detect // a 402 programmatically. ws.onclose = (event) => { console.log("disconnected:", event.code, event.reason); }; } </script>

Expected output

Right after connecting, expect a single hello frame describing your session:

json
{"type": "hello", "tier": "trader", "min_usd": 500.0, "chains": ["bsc", "avalanche"], "note": "Events below min_usd are filtered server-side."}

Then, roughly every 20 seconds of inactivity, a heartbeat keeps the connection alive:

json
{"type": "heartbeat", "ts": 1753300000.123}

And whenever a swap of at least $500 is detected on BSC or Avalanche, a swap frame arrives with the full event:

json
{ "type": "swap", "chain": "bsc", "dex": "pancakeswap_v2", "swapper": "0xabc...", "swapper_short": "0xabc…def", "swapper_label": "Binance 14", "tx_hash": "0x...", "explorer_url": "https://bscscan.com/tx/0x...", "token_in": {"symbol": "USDT", "amount": 12345.0, "address": "0x..."}, "token_out": {"symbol": "BNB", "amount": 12.5, "address": "0x..."}, "amount_usd": 12500.0, "pair": "USDT to BNB", "block": 38123456, "timestamp": 1753300000, "significance": "high" }

Only swaps at or above $500 notional are broadcast at all — smaller swaps never reach the socket, so there’s no need to filter noise below that floor yourself.

What to build next

The reconnect-with-a-fresh-ticket pattern in the Python script above is worth keeping even once the feed is stable in production: network blips and periodic upstream restarts are normal for any long-lived connection, and a single-use, short-lived ticket means there’s no long-lived credential sitting in a URL to worry about if a client is compromised.

Full endpoint reference is in the API documentation, and more integration patterns are in the code playbooks hub.

Unlock the live whale swap socket

Trader tier ($29/mo) includes the WebSocket feed, all symbols, and 3000 calls/day on the REST API. Start on the free tier to explore the REST endpoints first, then upgrade when you’re ready to go real-time.

View Trader plan — $29/mo
Start free — 200 calls/day, no card

Get live whale flow, funding, open interest and on-chain data across 3 exchanges from one API. Free tier, no credit card, upgrade any time.

Start free →
Try the live API console → (no account needed)