Skip to content

Connection Handshake

Some providers need messages sent on every new socket before they deliver data: a login, then a subscription. Alpaca, for example, expects an auth message whose token is computed from an API key and the current time, so a login recorded once cannot be replayed later.

A connection with a handshake asks a handler for those messages each time its socket opens:

The handler (a worker or service you run) holds the credentials and computes the messages. The WebSocketManager stores only where to ask, never what was sent, and contains no provider-specific logic.

Connecting with a handshake

await wsm.connect(
    "wss://stream.data.alpaca.markets/v1beta3/crypto/us",
    auto_reconnect=True,
    publish_topic="x.examples.alpaca.orderbook",
    handshake_topic="x.examples.alpaca.handshake",
    handshake_config={"symbols": "BTC/USD"},
)
ConnectRequest field Meaning
handshake.topic The handler's request topic.
handshake.timeout_ms Deadline for the handler's reply and for each expected frame. 0 means 10000; at most 120000.
handshake.config Forwarded to the handler as given, e.g. symbols or an account alias. Never put secrets here: it is stored with the connection.
publish_topic Publish frames to this topic from the first frame after the handshake, as StartPublish would. A separate StartPublish after Connect would miss the frames sent right after the subscription, such as a full order book.

The handler protocol

On each open the WebSocketManager publishes a request (request-reply over pub/sub) to handshake.topic:

  • ce-type com.virtufin.websocketmanager.connection.handshake
  • CloudEvent attributes correlationid and replytopic (sys.websocketmanager.instance.handshake.<instance>)
  • JSON data:
{"connectionId": "…", "url": "wss://…", "reason": "connect", "attempt": 1,
 "config": {"symbols": "BTC/USD"}, "tags": {}}

reason is connect, reconnect or handoff; attempt counts the tries for this open.

The handler publishes its reply to replytopic, with the same correlationid attribute, as JSON data:

{"messages": [
  {"text": "{\"action\":\"auth\",…}", "expect": "\"msg\":\"authenticated\"", "fail": "\"T\":\"error\""},
  {"text": "{\"action\":\"subscribe\",\"orderbooks\":[\"BTC/USD\"]}"}
]}

or {"error": "…"} to refuse. The WorkManager worker DevKit already honours replytopic and correlationid, so a worker can be a handler as is.

Each message has exactly one of text (a text frame) or base64 (a binary frame). The WebSocketManager sends them in order. For a message with expect or fail (regular expressions), it then reads frames until one matches: expect continues with the next message, fail fails the handshake. A message with only fail waits for one frame.

Frames read while waiting are not published. The last message normally has no pattern, so the data that follows it (such as Alpaca's first full order book) goes through the normal receive loop and is published.

Failure

The handshake fails on: no reply within timeout_ms, an error reply, a fail match, no expect match within timeout_ms, or the server closing the socket. The socket is then closed, and:

Open On failure
Connect The call fails with FAILED_PRECONDITION and the handshake's reason.
Reconnect Counts as a failed attempt: the reconnect backoff applies, and the connection ends disconnected after ReconnectMaxAttempts.
Handoff Retried with the reconnect backoff. The previous owner may still hold a provider's single connection slot (Alpaca: "connection limit exceeded") until it is fenced off, within about one fence renewal interval.

connected is published only after the handshake succeeds. The handshake is logged with its reason, attempt and duration, never with message contents.

Security

The handler's reply carries the messages, credentials included, over the platform's pub/sub to the instance's reply topic (sys.websocketmanager.instance.handshake.<instance>). Anyone who can subscribe to that topic through the API can read it. Prefer credentials that expire (a Broker API access token, an OAuth token) or that can do little (paper-trading or market-data-only keys) on a shared cluster. The WebSocketManager never stores or logs the messages.

Running a handler

The handler must be running whenever a socket opens, including reconnects and handoffs at any time. Run it as a long-lived worker or service, not inside a short-lived script. The examples run it in the script or notebook itself, so their connections re-authenticate only while they run.