Skip to content

WS: generic subscribe() silently drops unknown params keys #195

Description

@TexasCoding

Summary

Subscription.to_subscribe_params() only forwards keys listed in the hardcoded _SUBSCRIBE_FORWARD_KEYS tuple. Any other key the user passes via the public KalshiWebSocket.subscribe(channel, params={...}) escape hatch is silently dropped before the wire command is sent.

Failure modes:

  • params={'tickers': [...]} (plural in some channels' docs) is dropped — the server subscribes the consumer to a much broader stream than intended (all markets, all symbols).
  • params={'send_snapshot': True} (typo of send_initial_snapshot) is dropped — the consumer never gets a snapshot and has to wait for the first delta.
  • Any future server-added parameter is invisible to existing SDK clients with no warning.

This is the documented escape hatch for the two channels (control_frames, root) that don't have typed helpers, so users WILL hit it.

Location

kalshi/ws/channels.py:21-30, 48-54

Evidence

# kalshi/ws/channels.py:21-30
_SUBSCRIBE_FORWARD_KEYS = (
    "market_ticker",
    "market_tickers",
    "market_id",
    "market_ids",
    "shard_factor",
    "shard_key",
    "send_initial_snapshot",
    "skip_ticker_ack",
)

# kalshi/ws/channels.py:48-54
def to_subscribe_params(self) -> dict[str, Any]:
    """Build the params dict for the subscribe command."""
    result: dict[str, Any] = {"channels": [self.channel]}
    for key in _SUBSCRIBE_FORWARD_KEYS:
        if key in self.params:
            result[key] = self.params[key]
    return result  # ← unknown keys silently dropped

Recommended fix

Validate params against the channel's known parameter set at subscribe() time. Either:

Option A (minimal): Raise KalshiSubscriptionError from Subscription.__init__ (or to_subscribe_params) on unknown keys.

Option B (best): Define a per-channel dict[channel, frozenset[str]] of accepted params (with set semantics for the typed helpers + the public escape hatch), and raise on any key not in the channel's set. Drives off the AsyncAPI spec so it stays accurate.

Option C (highest fidelity): Add a typed SubscribeParams Pydantic model per channel with extra='forbid' to fail fast on typos — matches the existing request-body pattern.

Regression test: test_generic_subscribe_rejects_unknown_param_keys invokes ws.subscribe('orderbook_delta', params={'tickerz': ['X']}) and asserts KalshiSubscriptionError.

Severity & category

high / correctness, dx, ws

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingwsWebSocket-related

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions