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
@type session() :: %{ listen_key: String.t(), market_type: atom(), keepalive_endpoint: atom() | nil, keepalive_ms: pos_integer() }
Functions
@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.
@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.