Adapting to Binance’s stream shape changes

WS API userData envelope unwrap

Id: 67603c5a-14d0-4559-8709-34bcea467b02 Type: workaround Status: active Evidence: confirmed Source: commit 1503d59, merge 3b2e144, 2026-04-10

Binance removed the REST listenKey endpoints for Spot/Margin in February 2026. UBWA switched to the WS API subscription flow instead, which wraps userData events in {"subscriptionId": 0, "event": {...}}. binance_websocket() now unwraps that envelope before handing the payload to the existing normalization pipeline.

Reason: this is a forced adaptation to an external Binance API change, not a design choice — the envelope shape is Binance’s, not this library’s.

Catch-all replacing an event-type whitelist

Id: 86f7088c-eb13-45cd-90b8-73a365d4822a Type: decision Status: active Evidence: confirmed Source: commit 31d7fa1, fixes #41 See: adapters.md#shape-mismatches-dont-fail-the-normalizer-does-what-it-can-and-errors-belong-on-the-result — 79b64d5f-c693-4a82-9928-a8182c609124 — as of 2026-09-28

Previously, only explicitly-listed event types were unwrapped/normalized; anything else fell through and crashed with KeyError: 'data'. Replaced with a catch-all: any payload with a top-level e key and no data wrapper (e.g. listenKeyExpired) is now wrapped, instead of relying on an enumerated list of known event types.

Rejected alternative: keep extending the explicit whitelist every time Binance adds a new event type.

Reason: the whitelist approach means the library breaks on every new Binance event type until someone notices and adds it — the catch-all tolerates unknown-but-well-shaped events instead of crashing on them. This explains the number of bare except KeyError: pass blocks throughout unicorn_fy.py — see the next entry for how this pattern sits against the suite’s fail-loud convention.

Shape mismatches don’t fail: the normalizer does what it can, and errors belong on the result

Id: 79b64d5f-c693-4a82-9928-a8182c609124 Type: decision Status: active Evidence: confirmed Source: maintainer, 2026-09-28; the 29 except KeyError handlers in unicorn_fy.py; commit 31d7fa1 See: adapters.md#catch-all-replacing-an-event-type-whitelist — 86f7088c-eb13-45cd-90b8-73a365d4822a — as of 2026-09-28 See: https://github.com/oliver-zehentleitner/unicorn-binance-depth-cache-cluster — e325b6fb-117a-4f10-aa63-6d070b01188a — as of 2026-09-28 Revisit when: the unicorn-fy rewrite lands

unicorn-fy does not fail on a payload whose shape it doesn’t fully know: it normalizes what it recognizes and passes the rest through. The except KeyError handlers in unicorn_fy.py — a missing stream key on a non-combined stream or a subscription reply, a field an event type doesn’t carry — are that rule in code.

Reason: the maintainer’s rule: the normalizer must not fail, it does what it can. It is the same logic as the catch-all that replaced the event-type whitelist (See above): a new or varying shape from Binance is expected, not a broken invariant. This is where unicorn-fy stands apart from the suite’s fail-loud cases, like the cluster’s #6000 for an out-of-sync order book: there the data would be wrong; here it is only shaped differently.

Rejected alternative: raising on a missing key, fail-loud style. Rejected by that rule — the caller would lose the whole message over a field the normalizer only wanted to enrich.

Open part: the errors are to be attached to the result, so a caller can see what could not be normalized — today the handlers pass and the error is dropped silently. The planned rewrite of unicorn-fy is where that changes.

Init value False → {}

Id: 35a07689-77e0-4f96-bf06-b630e424f152 Type: incident Status: active Evidence: confirmed Source: commit f8d6341, fixes #44

unicorn_fied_data was initialized to False; an unmatched event type left it as False, and the next step (item assignment) crashed with TypeError: bool object does not support item assignment. Changed the default to {}.