Bourse.WS.ListenKey (bourse v0.7.0)

Copy Markdown View Source

Performs the REST round-trip the :listen_key auth pattern needs.

Bourse.WS.Auth.ListenKey resolves which endpoint issues and refreshes a key; this module calls them through Bourse.Dispatch and returns the key itself. The split keeps the pattern module network-free.

Why this runs before the socket opens

A listen key is not sent as a frame — it is embedded in the WebSocket URL. USD-M uses ?listenKey=...&events=...; COIN-M keeps its path segment. There is no post-connect handshake to fall back on, so Bourse.WS.connect/3 calls open/3 first and connects to the URL the key produces.

The failure this prevents is silent. A connection opened with a wrong, expired or absent key is accepted by the venue and then simply never delivers: verified against demo-fstream.binance.com 2026-08-06, where a real key produced an ORDER_TRADE_UPDATE for an order placed on the same account and a syntactically-valid bogus key produced nothing at all, both sockets reporting :connected throughout.

Keepalive

Binance expires an idle key 60 minutes after issue. keepalive/3 sends the authored refresh endpoint (PUT); Bourse.WS.Adapter schedules it from the keepalive_ms the pattern resolves.

Summary

Functions

Refreshes an issued key so the venue does not expire it.

Issues a listen key for the exchange's private stream.

Types

session()

@type session() :: %{
  listen_key: String.t(),
  market_type: atom(),
  keepalive_endpoint: atom() | nil,
  keepalive_ms: pos_integer()
}

Functions

keepalive(exchange, session, opts \\ [])

@spec keepalive(Bourse.Exchange.t(), session(), keyword()) :: :ok | {:error, term()}

Refreshes an issued key so the venue does not expire it.

Binance's refresh endpoint takes no key parameter — it extends whatever key the credentials own — so the session is passed only to name the endpoint. Returns {:error, :no_keepalive_endpoint} when the venue authors none, which is a configuration gap rather than a venue failure.

open(exchange, auth_config, opts \\ [])

@spec open(Bourse.Exchange.t(), map(), keyword()) ::
  {:ok, session()} | {:error, term()}

Issues a listen key for the exchange's private stream.

Returns {:ok, session} carrying the key plus what a caller needs to keep it alive. Errors are the venue's own — an unresolvable market type, an endpoint the generated module does not carry, a transport or business failure from the issuing call, or a response without a listenKey field.