WebSocket
A real-time feed of tracked-wallet trades. As each tracked wallet buys or sells, the trade is pushed to you within seconds.
wss://api.prysmatic-sol.xyz/ws
Every payload the server sends you costs 1 credit, charged the moment it is delivered, whether or not your client reads or processes it successfully. Use the wallet filter below to avoid paying for trades you do not care about.
Connecting
Authenticate with the same Bearer header used by REST. Do not put API keys in the URL, where they can be captured by proxy or access logs:
Authorization: Bearer <API_KEY>
When the connection opens, the server sends a free control message describing the authenticated session:
{
"type": "ready",
"auth": "api_key",
"auth_method": "authorization",
"channels": ["trades"],
"credits": 1200,
"metered": true
}
The connection is refused if the key is unknown or the balance is zero.
- Raw WebSocket
- Python SDK
import asyncio, json, websockets
API_KEY = "<API_KEY>"
async def main():
async with websockets.connect(
"wss://api.prysmatic-sol.xyz/ws",
extra_headers={"Authorization": f"Bearer {API_KEY}"},
) as ws:
await ws.send(json.dumps({"action": "subscribe", "channels": ["trades"]}))
async for raw in ws:
msg = json.loads(raw)
if msg.get("channel") == "trades":
print(msg["data"])
asyncio.run(main())
If you use websockets 15+, the header parameter is named additional_headers
instead of extra_headers.
import asyncio
from prysmatic_sdk import AsyncPrysmaticClient
API_KEY = "<API_KEY>"
async def main() -> None:
async with AsyncPrysmaticClient(api_key=API_KEY) as client:
async for trade in client.stream.trades():
print(trade.data)
asyncio.run(main())
The SDK handles the WebSocket connection, Bearer authentication, heartbeat messages, JSON decoding, reconnect backoff, and typed trade models.
Subscribing
Send a subscribe message for the trades channel:
{ "action": "subscribe", "channels": ["trades"] }
The server confirms the active subscription:
{
"type": "subscribed",
"channels": ["trades"],
"rejected_channels": [],
"wallets": [],
"limits": { "max_channels": 8, "max_wallets": 100 }
}
Filtering by wallet
You usually do not want every wallet, and you do not want to pay for trades you do not
care about. Add a wallets list (aliases from the Wallets API) and you
will only receive, and only be charged for, trades from those wallets:
- Raw WebSocket
- Python SDK
{ "action": "subscribe", "channels": ["trades"], "wallets": ["W12", "W37", "W205"] }
async for trade in client.stream.trades(wallets=["W12", "W37", "W205"]):
print(trade.data.wallet, trade.data.side, trade.data.mint)
- Omit
wallets(or send an empty list) to receive all tracked wallets. - Re-sending
subscribereplaces the filter, so you can change your watchlist at any time.
A trade from a wallet outside your filter is never delivered and never costs credits.
Unsubscribing
{ "action": "unsubscribe", "channels": ["trades"] }
The server replies with:
{ "type": "unsubscribed", "channels": ["trades"] }
Trade payload
{
"channel": "trades",
"data": {
"block_time": 1781404511,
"wallet": "W240",
"side": "buy",
"mint": "4Sm4YsjRrvSRQ4x1P86ncqumV3ZVgXxtFMKjiL8vpump",
"token_amount": "20415437641764",
"decimals": 6,
"sol_amount": 4.063765757,
"quote_mint": "So11111111111111111111111111111111111111112",
"quote_amount": "4063765757",
"program": "pump.fun"
}
}
walletis a wallet alias.sol_amountis already in SOL (not lamports).
Credits and limits
- Each delivered payload consumes 1 credit.
- Only the
tradeschannel is available to API-key clients. - One active connection per API key. If you open a second one with the same key, the
first is dropped with
{"type":"superseded"}. - When your balance is spent, the server sends
{"type":"balance_exhausted"}and closes the connection. Top up from the dashboard and reconnect.
Heartbeat
After a period of inactivity the server sends a heartbeat so it can detect and drop dead connections (and free the slot for other clients):
{ "type": "ping" }
You do not need to reply — receiving it is enough to keep your connection healthy, and a
ping is free (it does not consume credits). Just ignore any message whose type is not
a channel payload, as the example below does:
async for raw in ws:
msg = json.loads(raw)
if msg.get("channel") == "trades":
print(msg["data"])
# control messages ({"type": "ready" | "subscribed" | "ping" | "error" |
# "superseded" | "balance_exhausted"})
# are handled separately; "ping" can simply be ignored.
Limits
- Message size: a frame you send larger than 4 KB closes the connection (
1009). - Subscribe lists: at most 8
channelsand 100walletspersubscribe; anything beyond that is dropped. - Connections: one active connection per API key (see above), and the server has an
overall connection cap. If it is full, new connections are closed with
1013.
Close codes
| Code | Meaning | What to do |
|---|---|---|
1000 | Normal close. | Reconnect if you still want the feed. |
1008 | Policy: unknown/zero-balance key, or a control close (superseded, balance_exhausted). | Do not blindly reconnect — see below. |
1009 | A message you sent was too large. | Fix the client; reconnect. |
1013 | Server at capacity. | Back off and retry later. |
The control messages tell you why a 1008 is coming:
{"type": "balance_exhausted"}— you are out of credits. Top up, then reconnect. Reconnecting with a zero balance is refused.{"type": "superseded"}— another connection opened with the same key and took over. Do not reconnect a duplicate; you would just kick the other one.
Reconnecting
Network drops happen. When the connection closes unexpectedly (not balance_exhausted
or superseded), reconnect with exponential backoff and jitter rather than a tight
loop, and cap the delay:
- Raw WebSocket
- Python SDK
import asyncio, random, json, websockets
API_KEY = "<API_KEY>"
async def run():
delay = 1.0
while True:
try:
async with websockets.connect(
"wss://api.prysmatic-sol.xyz/ws",
extra_headers={"Authorization": f"Bearer {API_KEY}"},
) as ws:
await ws.send(json.dumps({"action": "subscribe", "channels": ["trades"]}))
delay = 1.0 # reset after a successful connect
async for raw in ws:
msg = json.loads(raw)
if msg.get("type") == "balance_exhausted":
return # stop: top up before reconnecting
if msg.get("channel") == "trades":
handle(msg["data"])
except Exception:
await asyncio.sleep(delay + random.uniform(0, delay)) # backoff + jitter
delay = min(delay * 2, 60)
import asyncio
from prysmatic_sdk import (
AsyncPrysmaticClient,
InsufficientCreditsError,
StreamSupersededError,
)
API_KEY = "<API_KEY>"
async def run() -> None:
async with AsyncPrysmaticClient(api_key=API_KEY) as client:
async for trade in client.stream.trades(reconnect=True):
handle(trade.data)
try:
asyncio.run(run())
except InsufficientCreditsError:
print("Top up before reconnecting.")
except StreamSupersededError:
print("Another connection opened with the same key.")
The SDK reconnects with exponential backoff and jitter by default. It stops and raises
typed exceptions for account-level control messages such as balance_exhausted and
superseded.
Re-subscribe (and re-send your wallets filter) on every reconnect — subscriptions do
not survive a dropped connection.