# `Bourse.WS.ListenKey`
[🔗](https://github.com/ZenHive/bourse/blob/main/lib/bourse/ws/listen_key.ex#L1)

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.

# `session`

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

# `keepalive`

```elixir
@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`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
