ThemeFontsMountReady
SEND
DocsPricingFeaturesComparisonsHelpSign inRequest access →

SEND / Docs

WebSocket Events

One multiplexed socket, MessagePack frames, and the delivery guarantees that come with them.

← DocsAPI Reference9 min readUpdated August 10, 2026

Transport

SEND exposes a single multiplexed WebSocket. You subscribe to topics over one connection rather than opening a socket per feed.

The two directions do not share an encoding. Client frames are JSON text. Server frames are MessagePack binary. There is no JSON mode for server frames, so a client that cannot decode MessagePack will see nothing but bytes.

  • GET /api/v1/ws?ticket=<uuid> -- the authenticated feed.
  • GET /api/v1/ws/public -- no authentication. The connection is given a guest identity and may subscribe to public topics only.
  • POST /api/v1/auth/ws-ticket -- mints the single-use ticket the authenticated endpoint requires.

Connecting

Authentication happens before the upgrade, not on it. There is no Authorization header on a WebSocket handshake, so a short-lived ticket stands in for one.

Only a login token can mint a ticket. The ticket endpoint validates a JWT directly and never reaches the API-key path, so an API key cannot open a WebSocket -- presenting one returns 401.
  1. POST /api/v1/auth/ws-ticket with your bearer token. The response data is { ticketId }.
  2. Open the socket at /api/v1/ws?ticket=<ticketId>.
  3. The ticket is consumed atomically on first use and expires 30 seconds after it is issued.
  4. A ticket that is expired, unknown, or already used fails the upgrade with 401 and the message "invalid or expired WebSocket ticket".
  5. Mint a fresh ticket for every connection attempt, including every reconnect.

Sending and Receiving Frames

Four client message types exist, each tagged by a type field.

The server answers with data, subscribed, unsubscribed, error and pong frames. A data frame is { type: "data", topic, seq, data }, where seq is a per-topic monotonic counter you can use to detect a gap. It cannot be used to resume: there is no replay and no message history.

  • {"type":"subscribe","topic":"balances"} -- subscribe. An optional filter object narrows parameterized topics, and tokenAddress is the only filter field, used by candles and prices.
  • {"type":"unsubscribe","topic":"balances"} -- unsubscribe.
  • {"type":"ping"} -- answered with a pong frame.
  • {"type":"viewport","walletSetIds":[],"walletIds":[]} -- declares what your client currently displays so those balances stay warm. Fire and forget, with no acknowledgement.

Topics

Topic names are camelCase on the wire. Public topics are available on both endpoints; private topics are delivered only to the account that owns the underlying record.

  • Public: swaps, prices, candles, tweets, publicSwaps, dexActivity, platformStats.
  • Private: balances, portfolio, aging, volume, notifications, exchanges, launches, walletGeneration, projectUpdates, swapResults, swapSubmissions.
  • balances and portfolio are routed by wallet ownership; the remaining private topics are routed by the user id carried on the record.
  • Subscribing to a private topic on the public endpoint is refused with error code TOPIC_FORBIDDEN. A handful of further topics are reserved for operators and are refused with TOPIC_ADMIN_ONLY.
  • An unrecognised topic name fails the frame parse itself, so it surfaces as INVALID_MESSAGE rather than a topic-specific error. exchangeStatus is one of these -- it was retired, and all exchange progress now arrives on exchanges.

The Socket Is a Delta Stream

Subscribing does not hand you current state. Only swaps sends an initial snapshot, and it is a single frame carrying the latest cached trade. Every private topic sends nothing at all until its next live update, which may be seconds or hours away.

Fetch a REST baseline first, subscribe, then apply frames on top of it. A client that starts from an empty object and waits for the socket to fill it renders blanks.

Optional fields are omitted from a frame rather than sent as null or zero. An absent field means "unchanged -- keep your last known value", never "zero". Writing 0 for an absent USD value renders a merely-unpriced position as $0.00 and makes unrealized P&L equal realized P&L. Every decimal money value is a string.

Delivery Guarantees

The socket is a performance layer, not a system of record. Design the consumer accordingly.

  • Frames are best-effort and at-least-once. They can arrive twice, and they can be missed entirely across a reload, a disconnect, or a throttled background tab.
  • Terminal swap results are re-emitted by the backend on purpose, so duplicates are expected. Dedupe on a stable id.
  • Anything that has to be correct -- balances, batch outcomes, ledgers -- must be reconciled against a REST read.

Limits and Disconnects

The server pings every 30 seconds and drops a connection that has not answered within 10. A stalled peer is dropped after 10 seconds on a single send, and a connection that keeps failing to accept frames is closed as a slow consumer after 50 consecutive failures. Sessions are revalidated every 5 minutes, so a deactivated account's socket is torn down rather than left open.

Drive your own heartbeat off a count of unanswered pings rather than a wall-clock deadline. Browsers throttle timers in background tabs, so a wall-clock check fires late and force-closes a socket that was healthy the entire time.
  • Five concurrent connections per user. At the cap a new connection is admitted and your oldest one is evicted -- the sixth connection does not fail, it displaces the first.
  • Close code 4000, server shutting down -- reconnect with normal backoff.
  • Close code 4001, session no longer valid -- do not reconnect; re-authenticate first.
  • Close code 4002, slow consumer -- reconnect with extra backoff.
  • Close code 4003, connection limit -- reconnect only after a hard minimum delay.
  • Every close is preceded by an error frame naming the reason, and the public endpoint enforces its own connection limits, answering 429 RATE_LIMITED on the upgrade when they are exceeded.
←Previous
Swaps API
Was this article helpful?

SEND

Execution infrastructure for Solana. Coordinated execution across thousands of wallets, with a footprint that reads as thousands of strangers.

TwitterDiscordGitHub

Product

FeaturesPricingComparisonsChangelogDocumentation

Company

AboutAmbassador ProgramContact

Legal

PrivacyTerms

Support

Help CenterDiscordTwitter

© SEND 2026 — All rights reserved

Powered by ChainKit