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

Classifies WebSocket subscribe acknowledgements and rejections.

Venues deliver subscribe outcomes in two ways:

1. **Correlated** (deribit JSON-RPC) — `ZenWebsocket.Client.send_message/2`
   returns `{:ok, envelope}` because the outbound frame carries an `id`.
2. **Asynchronous** (alpaca, bybit, okx, hyperliquid, derive, binance,
   lighter) — send
   returns `:ok` and the venue reply arrives as
   `{:websocket_message, frame}` or `{:websocket_unmatched_response, frame}`
   (derive replies with a JSON-RPC envelope even when the request had no id).

`Bourse.WS.subscribe/3` uses this module so both paths resolve to the same
caller contract: `:ok` on accept, `{:error, {:subscription_rejected, frame}}`
on reject.

# `classification`

```elixir
@type classification() :: :success | {:success, :data} | :not_ack | {:rejected, map()}
```

# `classify`

```elixir
@spec classify(String.t(), map() | [map()]) :: classification()
```

Classifies a subscribe outcome frame for `exchange_id`.

Returns:
- `:success` — venue accepted the subscription
- `{:success, :data}` — venue accepted, and the frame is also the first snapshot
  (re-queue after treating it as the acknowledgement)
- `{:rejected, frame}` — venue rejected it (frame is the raw envelope)
- `:not_ack` — not a subscribe outcome (data/heartbeat/other); leave in mailbox

# `to_result`

```elixir
@spec to_result(classification()) ::
  :ok
  | {:error,
     :unexpected_subscription_response | {:subscription_rejected, map()}}
```

Turns a classification into the public `subscribe/3` return value.

A non-ack correlated reply is an unexpected protocol response, not evidence
that the venue accepted the subscription.

---

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