Header logo.
small hallucinations
homeyearstagsaboutrss

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:

  1. Connect to the endpoint.
  2. Send an authentication frame.
  3. 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