Alpaca WebSocket Authentication with WebSockex
I was trying to stream market data through Alpaca's WebSocket endpoint.
Alpaca's documentation lists the following three steps for receiving market data from the stream:
- Connect to the endpoint.
- Send an authentication frame.
- Send a subscription frame.
Step 2 proved tricky.
The library I'm using is WebSockex. As a best practice, you should either send the auth frame in the handle_connect function or reply with one from handle_frame when the “connected” message is received. In this particular case, both approaches failed or timed out.
The handle_connect function is called immediately after the WebSocket connection is established. From handle_connect, you cannot return {:reply, auth_frame, state}; you can only return {:ok, state} or {:close, state}.
If you want to send the auth frame to the server, you have to use WebSockex.send_frame(pid, frame). ChatGPT keeps telling me to do the following, although it invariably causes a CallingSelfError. Apparently, you cannot simply ask yourself—the same process—to send a frame.
1def handle_connect(_conn, state) do
2 # This will cause an error.
3 WebSockex.send_frame(self(), {:text, auth_frame})
4 {:ok, state}
5end
But in the handle_frame function, you can return a tuple like this: {:reply, auth_frame, state}. You only need to pattern-match the incoming frames. When you see “connected,” return that reply tuple. This would have worked had it not timed out. (Curiously, replying with a subscription frame worked.)
In the end, I sent the auth frame inside the start_link function.
1 def start_link(state) do
2 connected = WebSockex.start_link(@uri, __MODULE__, state)
3
4 case connected do
5 {:ok, pid} ->
6 WebSockex.send_frame(pid, {:text, auth_json})
7 {:ok, pid}
8
9 {:error, reason} ->
10 Logger.info(reason)
11 end
12 end
