Docs
    Guide · Beta

    WebSocket

    Stream decoded blocks, DEX swaps, and token transfers over a single persistent connection. Path-based subscription, server-side filters, replay on reconnect, and per-message billing.

    What you get

    • Solana DEX swaps solana@swaps. Raydium, Orca, Meteora, Pump.fun and more, normalized per trade.
    • Solana transfers solana@spl_transfer, solana@system_transfer. SPL, SPL-2022, and native SOL.
    • EVM DEX swaps mainnet@swaps, base@swaps, and 6 more EVM chains.
    • EVM ERC-20 transfers mainnet@erc20_transfers, plus the same 8 EVM networks.

    Live catalog at ws.pinax.network/streams. Supported networks at api.pinax.network/v1/networks.

    Subscription URL

    Channels are encoded in the URL path — no JSON-RPC subscribe message needed.

    wss://ws.pinax.network/ws/<network>@<table>?token=<API_KEY>

    Single channel, raw payload:

    wss://ws.pinax.network/ws/solana@swaps?token=$PINAX_KEY

    Multi-channel (wrapped envelope — { "stream": "<id>", "data": <raw> }):

    wss://ws.pinax.network/ws/solana@swaps/mainnet@erc20_transfers?token=$PINAX_KEY

    Wildcards work on either side — *@swaps, solana@*. Bare /ws is HTTP 400; use /ws/*@* to opt into everything.

    Messages vs events — billing

    Two counters matter, and they are not the same number:

    • Messages — one WebSocket frame per (network, table) per block. This is the billable unit.
    • Events — items inside each message's events[] array. A single message can carry dozens of swaps or transfers.

    Pricing is $0.00005 per message (= $0.50 per 10K). A package that emits two tables produces two messages per block — one per table — regardless of how many events each contains. Use server-side SET_FILTER to reduce billable messages further; heartbeats and protocol frames are free.

    Sample envelope (Solana swaps):

    {
      "network": "solana",
      "table": "swaps",
      "block_num": 422102644,
      "block_hash": "6AEzeSFDZstnhagF1D2FsCPNVELz1YBTvHFMHDSNP4fs",
      "timestamp": "2026-05-25T16:20:05Z",
      "timestamp_seconds": 1779726005,
      "module_hash": "411d6a46…b295",
      "events": [
        { "protocol": "pumpfun_amm", "user": "B6r52E…YUbC", ... },
        { "protocol": "raydium_clmm", "user": "9KaPDQ…X4tN", ... }
      ]
    }

    Authentication

    Every connection needs your API key. Two equivalent options:

    • Header (server libs): Authorization: Bearer <jwt>
    • Query param (browsers — can't set headers on the WS upgrade): ?token=<jwt>

    Same JWT as the rest of Pinax — RPC, Firehose, Token API. Quota is shared.

    Reconnects & replay

    The server retains a 600s window per package on disk. On reconnect, pass ?from_timestamp=<n> (epoch seconds or ISO-8601). Every retained block with block_num > n is replayed oldest-first before the live stream resumes.

    wss://ws.pinax.network/ws/solana@swaps?from_timestamp=1715619600
    wss://ws.pinax.network/ws/solana@swaps?from_timestamp=2026-05-25T16:00:00Z

    If n falls below the oldest retained block, a gap lifecycle message fires — backfill via Substreams gRPC with an explicit start_block. Wildcards skip replay.

    Server-side filtering

    Drop non-matching events before they hit the wire — reduces billable messages, not just bandwidth. String equality only; fields AND; values OR; missing field counts as a miss.

    { "method": "SET_FILTER",
      "params": { "selector": "solana@swaps",
                  "filter": { "protocol": ["pumpfun_amm", "raydium_clmm"] } },
      "id": 1 }

    Per-selector. Wildcards always pass through. Max 16 keys, 64 values per filter. Top-level fields (block_num, network) are not filterable — only keys inside events[*].

    Use cases

    Live trade & transfer feeds

    Push DEX swaps and ERC-20 / SPL transfers into a UI the moment they confirm. One socket per network@table — no polling, no per-block REST fan-out.

    Real-time alerting

    Apply server-side SET_FILTER rules to drop non-matching events on the wire. Trigger Discord, Slack, or webhook notifications only on the trades you actually care about.

    Reorg-aware indexers

    undo lifecycle events surface chain reorganizations explicitly. Roll back materialized state past last_valid_block instead of rebuilding from scratch.

    AI agents & MCP

    Stream pre-parsed swaps into an agent loop with no schema work. Each message ships the protocol, the user, and the trade — ready to be summarized or acted on.

    Frequently asked

    Why is this marked Beta?
    Stream protocol is stable but channel names, payload field naming, and the replay window may evolve. Pin a module_hash in production and watch for stream lifecycle fatal messages.
    How many connections can I open?
    Beta caps per-key concurrency at 4 connections. Multiplex many channels over a single socket using the multi-channel URL form instead of opening one per stream.
    What happens to slow consumers?
    Each connection has a 1024-message buffer. If you fall behind, messages are dropped per-message rather than blocking ingest — so a sluggish consumer never backpressures other subscribers.
    Are heartbeats billable?
    No. WebSocket ping/pong frames are protocol-level and free. The server pings every 180s; clients that don't pong within 600s are closed.
    Are values in events[] strings or numbers?
    Strings on the wire — DatabaseChanges proto convention. Numeric parsing is the consumer's job. Use the field's known decimals (e.g. token decimals) to scale.
    How do I get older history?
    The replay window is 600s. For deeper backfills, use Substreams gRPC directly with a start_block, or query Token API for indexed history.

    Related