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 first
Connect, - every automatic reconnect (
auto_reconnect), - every actor handoff to another instance (see Socket Ownership and Recovery).
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
correlationidandreplytopic(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.