Version 2.0.1.1 Released

Date: 17th May 2026

Major Feature Release: WhatsApp Bot Integration — Event-Driven Alerts + Slash-Command Queries, Plus a WebSocket Reliability Sweep Across 14 Brokers, New Broker Integrations (IIFLCapital Streaming, Groww Option Chain + WS Depth, Upstox GLOBAL_INDEX), and Per-Install Fernet Salt Rotation with Crash-Safe Auto-Migration

This release spans 70+ commits since v2.0.1.0. The headline change is WhatsApp — a self-hosted, event-driven WhatsApp bot that fires order alerts on the same event bus that drives Telegram, accepts slash-command queries (/orderbook, /positions, /quote, …) from the operator's own phone, and exposes a single send endpoint over REST. Pairing happens once from the OpenAlgo admin UI with a QR scan; the encrypted session blob is then stored in openalgo.db and the bot auto-reconnects on every server boot. Alongside WhatsApp, this release lands a WebSocket reliability sweep — subscribe batching across 6+ brokers, reconnect hardening across 4+ brokers, file-descriptor leak fixes in two streaming layers, and proxy-level fixes (ZMQ bind, mode normalization, request_id correlation). Three new broker integrations land: IIFLCapital streaming, Groww option chain + WS depth, and Upstox GLOBAL_INDEX world feeds. Security: a per-install random FERNET_SALT now feeds the Fernet KDF, with a crash-safe online migration of existing ciphertext.


Highlights

  • WhatsApp Bot — event-driven alerts + slash-command queries — Brand-new /whatsapp admin page with auto-rotating QR pair flow, a single trader-facing POST /api/v1/whatsapp/notify endpoint that accepts text + image + document attachments, a dedicated worker thread that satisfies wars's PyO3 unsendable contract, and a subscribers/whatsapp_subscriber that wires every order topic on the existing event bus so order/position/batch events fire WhatsApp messages in parallel with Telegram. Single-user gate via WhatsApp's own is_from_me=True mark — random contacts who message the operator's number cannot drive the bot.

  • client.whatsapp() in the openalgo Python SDK (1.0.50) — One unified call for every common case: send to self, to a single E.164 number, to up to 5 numbers (ToS-safety cap), with text, image, or document payloads. wait_for_delivery=True by default so the response carries a real per-recipient delivery report.

  • WebSocket reliability sweep across 14 brokers — Subscribe batching for Kotak (HSI multi-scrip frames + 50ms debounce), AliceBlue, Nubra, Shoonya, Angel, Flattrade. Reconnect hardening for Dhan (auto-resubscribe + data-stall watchdog), Dhan-Sandbox (eventlet-safe asyncio + single-loop reconnect), Upstox (stall-vs-network reconnect logging), Fyers TBT (batch queue + pong validation + exponential backoff + health check). Cold-subscribe latency cut ~5× on Shoonya; Zerodha lost a ~4s sleep floor.

  • WebSocket-proxy + client fixes — Bind ZMQ publisher to ZMQ_HOST instead of all interfaces (#1378), normalize subscribe/unsubscribe mode case-insensitively (#1375), correlate ack via request_id (#1376), route cache invalidation through SharedZmqPublisher to eliminate the PUB→PUB topology (#1374).

  • Three new broker integrationsIIFLCapital streaming (full WS adapter; file-descriptor leaks closed across reconnect cycles, #1416 + #1430), Groww (full option chain + WS depth, expiry filter for expired contracts, broker-symbol mangling fix, #1392), Upstox GLOBAL_INDEX (US30 / JAPAN225 / HANGSENG world feeds via the existing Upstox WS adapter).

  • Per-install random Fernet saltFERNET_SALT env var is now provisioned per install (32-byte hex, generated by utils/env_check.py) and feeds the Fernet KDF in database/auth_db.py. Crash-safe online migration moves existing ciphertext from the legacy static salt to the new one; if the process dies mid-migration the next boot resumes from the persisted state. Tightens broker-auth-token confidentiality and is the same domain-separated salt the new WhatsApp session blob uses.

  • Platform version bump2.0.1.02.0.1.1. SDK pin (openalgo) 1.0.491.0.50.


WhatsApp Bot — feature deep dive

WhatsApp brings parity with Telegram for both outbound alerts and interactive command queries, with security choices tuned for the single-user-per-deployment model OpenAlgo runs under.

Architecture in one sentence: the wars (PyO3 over whatsapp-rust) library hosts a fully linked WhatsApp Web device inside the Flask process; outgoing alerts flow event-bus → whatsapp_subscriberWhatsAppBotThreadwars.send(); incoming slash-commands flow wars.on_messageis_from_me=True gate → command dispatcher → OpenAlgo SDK call → reply via the same bot thread.

Pairing (admin only)c44a420b:

  • POST /whatsapp/pair (session-cookie auth) spawns a temp-DB wars instance, registers an on_qr callback that streams whatsapp_qr SocketIO events with a data:image/png URL, and waits on wait_until_ready(timeout=300) for the phone-side scan

  • QR refreshes ~every 30 seconds; React /whatsapp page swaps the <img> source on each whatsapp_qr event without polling

  • On pair success: export_session() → Fernet-encrypted → persisted to whatsapp_config.session_blob in openalgo.db → temp file unlinked

  • Pair-code path (POST /whatsapp/pair with phone parameter) for users who prefer not to scan a QR

Encryption at restdatabase/whatsapp_db.py:

  • Fernet key derived via PBKDF2-SHA256(API_KEY_PEPPER, FERNET_SALT + b":whatsapp-session", 100k iters), 32-byte output, base64-urlsafe encoded

  • Domain separator (:whatsapp-session) means the same (PEPPER, FERNET_SALT) pair derives different Fernet keys for broker auth tokens, Telegram bot tokens, and WhatsApp session blob — compromising one channel's ciphertext gives no leverage against the others

  • Idempotent SQLite ALTER TABLE ADD COLUMN migration runs at every init so existing installs pick up the owner_user_id / owner_username columns without manual schema work

The unsendable PyO3 trapc44a420b:

  • wars.WhatsApp is #[pyclass(unsendable)] — every method call panics if invoked from a thread other than its creator

  • Solution: a dedicated WhatsAppBotThread owns the wars instance for its lifetime; request threads enqueue (op, args, result_holder, event) on a queue.Queue and wait on a threading.Event for the worker to dispatch wars.send()

  • Re-entrant: command handlers (which wars dispatches on the bot thread itself) bypass the queue via a threading.get_ident() == self._bot_thread_id check so they don't deadlock on themselves

  • Same shape Telegram uses for python-telegram-bot, for the same reason

REST surface — intentionally narrowrestx_api/whatsapp_bot.py:

  • POST /api/v1/whatsapp/notify is the only public endpoint

  • Pairing, start/stop, config, users, broadcast, stats, preferences live behind the session-authed /whatsapp/* blueprint — admin only

  • A leaked API key cannot re-pair the device, enumerate linked recipients, change rate limits, or fan out to the operator's contact list

  • Hard precheck — every send path refuses with 409 "WhatsApp is not paired or not connected. Pair the device first from the /whatsapp page in OpenAlgo before sending." if is_ready() is false; we explicitly do not queue when unpaired

Send paths — one unified call:

  • client.whatsapp("Build #482 deployed") → wars's single-arg send("text") form, routes to the paired device's own number (no need to know own JID)

  • client.whatsapp("hi", to="919876543210") → single recipient

  • client.whatsapp("alert", to=[...]) → broadcast (capped at 5 server-side — anything beyond is dropped; ToS-safety guardrail)

  • client.whatsapp("EOD chart", to="919...", image="/srv/charts/nifty.png", caption="...") → image with caption

  • client.whatsapp("report", username="alice", document="/srv/reports/eod.pdf", filename="EOD.pdf") → document

  • Attachment paths validated against WHATSAPP_ATTACHMENT_ROOTS allowlist (default: <openalgo>/db/attachments/); paths with .., paths under /etc /proc /sys /root /var/log /C:\Windows, or paths that resolve outside the allowlist are rejected with 400 image_path is not allowed

Inbound commandsservices/whatsapp_bot_service.py:

Command
Maps to

/help, /menu, /start

Command list

/status

Bot connection + paired status

/orderbook

client.orderbook()

/tradebook

client.tradebook()

/positions

client.positionbook()

/holdings

client.holdings()

/funds

client.funds()

/pnl

client.pnl() or client.positionbook()

/quote SYM [EXCH]

client.quotes()

/closeall

client.closeposition()

/mode

live or analyze

Auth: the bot only responds when is_from_me=True (WhatsApp's multi-device protocol marks messages mirrored from the operator's primary phone with this flag). Random contacts who message the operator's WhatsApp number arrive with is_from_me=False and are silently ignored. The OpenAlgo SDK calls run with the operator's API key, looked up from auth_db by the owner_username captured at pair time — there is no /link flow because there is no second user to authorize.

Auto-reconnect on app bootapp.py:_autostart_whatsapp_bot:

  • Background thread spawned during _init_databases_and_schedulers (after db_ready.set())

  • Loads the encrypted session blob, calls wars.WhatsApp.from_bytes(blob), starts the worker thread, registers handlers

  • No QR scan on restart — is_ready() flips to true within ~1s of boot

  • If wars isn't installed (fresh checkout that hasn't uv sync'd), the autostart logs a warning and degrades gracefully — the rest of the Flask app boots normally

Frontendfrontend/src/pages/whatsapp/WhatsAppIndex.tsx:

  • Single-page React UI: Status card with Pair QR + Disconnect button, "Send a one-off message" card, no Linked-Users table (single-user app)

  • SocketIO subscriptions for whatsapp_qr (auto-rotates the QR image), whatsapp_pair_code, whatsapp_paired, whatsapp_pair_status, whatsapp_status

  • New MessageCircle icon in the profile dropdown so WhatsApp visually differs from Telegram's MessageSquare

RUST_LOG quietingservices/whatsapp_bot_service.py:

  • Three known-noisy targets silenced at module import (before any import wars): wacore::send (stale-device warnings), whatsapp_rust::message (PN→LID migration chatter), wacore_libsignal::protocol::session_cipher (no-current-session errors that the upper layer already handles)

  • os.environ.setdefault("RUST_LOG", ...) — operator can still override via shell or .env for diagnostics

openalgo Python SDK 1.0.50 — released to PyPI alongside this version:

  • New WhatsAppAPI mix-in adds client.whatsapp(...) with the four recipient forms and image/document payloads

  • Mirrors the client.telegram() ergonomics for traders already familiar with the SDK


WebSocket reliability sweep — 14 brokers touched

Broker
Change
Commit

IIFLCapital

Full websocket adapter integrated + file-descriptor leaks closed across reconnect cycles

0ad69cf3 (#1416), 15179371 (#1430)

Paytm

NSE_INDEX/BSE_INDEX symbol normalization + fd-leak fixes in streaming layer + batch ws subscribe + graceful empty for option chain / OI tracker / historical

8dba1ea3 (#1413)

Kotak

Batch subscribe via HSI multi-scrip frames + cut batch debounce 500ms → 50ms + log emitted scrips

8fef2a57 (#1399)

Groww

Full option chain + websocket depth integration + drop expired expiries + stop mangling broker symbols

440b78ef (#1392)

AliceBlue

Batch ws subscriptions like Zerodha + event-driven connect + leading-edge subscribe debounce

09667a6e (#1389)

Dhan-Sandbox

Eventlet-safe asyncio + heartbeat align + single-loop reconnect + batch queue

b5675653 (#1344)

Nubra

Coalesce per-symbol subscribes into batched SDK calls

6473aa2e (#1366)

Shoonya

Batch queue + auth-fail short-circuit + env-var tuning + interruptible sleeps; cold-subscribe latency cut ~5× via leading-edge debounce

4b8578d6 (#1381)

Dhan

Auto-resubscribe on reconnect + data-stall watchdog

d214a598 (#1372)

Upstox

Distinguish stall-triggered reconnects from network-induced ones in logs

42927546 (#1357)

Angel

Subscribe batch-queue to coalesce per-symbol bursts + defensive .get() in place_order response handling

790cc64d (#1352), a4bdac18 (#846)

Zerodha

Remove ~4s sleep floor from subscribe path + add auth-fail short-circuit + interruptible sleeps + wire MCX_INDEX through quote/history/depth/ws

659d53ab, f21f40cc (#1371), cd4095eb (#1385)

Fyers

Harden TBT client with batch queue, pong validation, exponential backoff, and health check

463a3004 (#1361)

Flattrade

Batch-queue subscriptions like Zerodha adapter

ed37dbc2 (#1341)

Kotak

Align place/modify order payload with official Neo spec

b06ef4a8 (#1398)

WebSocket-proxy + client layer:

  • dd9cb64c (#1378) — fix(websocket_proxy): bind ZMQ publisher to ZMQ_HOST instead of all interfaces

  • 6ddff2bb (#1375) — fix(websocket_proxy): normalize subscribe/unsubscribe mode case-insensitively

  • 9a0f5e42 (#1376) — fix(websocket_client): correlate subscribe/unsubscribe acks via request_id

  • 521ea129 (#1374) — fix(cache_invalidation): route through SharedZmqPublisher to eliminate PUB->PUB topology

  • 25eed728fix(ui/websocket-test): keep LTP card live when Depth is also subscribed

  • f8a1ff4f (#1386) — chore(websocket): remove dead get_supported_brokers_list()

  • 06d4cb34docs(audit): add WebSocket broker priority audit


New broker integrations + exchange expansion

  • 0ad69cf3 / 15179371 — IIFLCapital websockets integrated + broker hardening + option-chain perf

  • 440b78ef — Groww full option chain + websocket depth integration

  • c0335582 — Upstox GLOBAL_INDEX world feeds (US30 / JAPAN225 / HANGSENG, plus IFSC-routed GIFTNIFTY) on indices and indicators

  • cd4095eb — Zerodha MCX_INDEX wired through quote/history/depth/ws

  • ca15b333 — Groww NSE_INDEX/BSE_INDEX in historical (#1338/#1342)

  • 11778af5 — Symbol search surfaces FUT-only MCX underlyings (#1385)


Security hardening — Fernet per-install salt

The Fernet KDF in database/auth_db.py (which encrypts broker auth tokens) previously used a hardcoded static salt. This release lands a per-install random salt with a crash-safe online migration.

  • 2f2e9beefix(security): rotate Fernet to per-install salt with crash-safe auto-migration

  • e3c5285d — place FERNET_SALT adjacent to API_KEY_PEPPER in .env, handle the 4 file-state cases cleanly (placeholder / valid / missing / mid-migration)

  • a1455ff1 — atomic .env rewrite falls back to in-place on Docker bind mounts

  • 84922078 — degrade gracefully when .env is unwritable + pin Docker UID 1000 (#1394)

  • 2981ff52 — post-FERNET_SALT cleanup — security perm-check, dedupe, pool invalidate (#1394)

  • b9301b78 — never recommend rotating API_KEY_PEPPER on a populated DB (carried in from late v2.0.0.9 work, re-asserted here)

What this means operationally:

  • New installs: utils/env_check.py generates a 32-byte hex FERNET_SALT, writes it adjacent to API_KEY_PEPPER in .env, and uses it directly. No migration runs.

  • Existing installs upgrading to 2.0.1.1: on first boot, env_check detects the placeholder/missing state, generates the new salt, re-encrypts every broker-auth-token ciphertext on the fly with the new key, and atomically swaps. If the process dies mid-migration, the persisted FERNET_SALT lets the next boot resume cleanly.

  • Same salt entropy feeds the new WhatsApp session blob via the :whatsapp-session domain separator.


UI + frontend

  • cd692653feat(orderbook): make Quantity editable in Modify Order dialog

  • f968b403fix(ui): align table content with stats cards on Positions/Orderbook/Holdings/Tradebook

  • d3aa8b6bfix(frontend): auto-reload on stale-chunk import failure (#1393) — fixes the "ChunkLoadError" trader sees after a deploy when the browser is still holding the old index-*.js reference

  • d63ec927 — drop hero gradient on the home page, use solid text-primary

  • 3f4e2b2b — home: "New in V2 — 12-Tool Options Analytics Suite" pill

  • 624f8726 — home: Integrates With + Made for AI sections

  • 61370758 / 54d83c07 — restyle the MCP OAuth consent page to match OpenAlgo dashboard + fix alignment


Configuration changes

.sample.env:

  • FERNET_SALT — new placeholder line auto-rotated on first boot, placed adjacent to API_KEY_PEPPER

  • No new keys for WhatsApp — WHATSAPP_KEY_SALT reuses the existing FERNET_SALT with a :whatsapp-session domain suffix (zero new env vars to manage)

  • Optional: WHATSAPP_ATTACHMENT_ROOTS (comma-separated absolute dirs; defaults to <openalgo>/db/attachments/)

  • Optional: RUST_LOG — defaults to a filter that silences three known-noisy wars/whatsapp-rust modules

pyproject.toml:

  • version = "2.0.1.1"

  • New: wars==0.1.3 dependency (also added to requirements.txt + requirements-nginx.txt)

  • SDK pin (openalgo) 1.0.491.0.50

utils/version.py:

  • VERSION = "2.0.1.1"

requirements.txt + requirements-nginx.txt:

  • wars==0.1.3 added after python-telegram-bot==22.6

  • openalgo==1.0.49openalgo==1.0.50

frontend/src/stores/alertStore.ts:

  • New whatsapp toast category alongside telegram


Database schema

New tables created by database/whatsapp_db.py on first boot:

Table
Purpose

whatsapp_config

Singleton row holding the Fernet-encrypted session blob + bot config + owner identity

whatsapp_users

Optional linked recipients (unused in single-user mode, retained for future multi-recipient deployments)

whatsapp_command_logs

Slash-command audit log (sensitive args like /link's api_key are scrubbed before write)

whatsapp_notification_queue

Retry queue for failed alerts (currently unused — single-user model drops on not-paired instead of queueing)

whatsapp_user_preferences

Per-user notification toggles + summary time + language + timezone

Idempotent PRAGMA table_info migration adds owner_user_id + owner_username columns to whatsapp_config on existing installs.


Dependencies

  • wars==0.1.3 added — PyO3 binding over the whatsapp-rust crate; provides the WhatsApp Web client. Wheels available for Python 3.12+ via abi3.

  • openalgo SDK pin: 1.0.491.0.50 (PyPI: https://pypi.org/project/openalgo/1.0.50/)

  • urllib3 2.6.32.7.0 (4519f55f) — clears 8 Dependabot high-severity alerts

  • axios 1.151.16, python-multipart 0.0.260.0.27, pin pip>=26.1 (6d61e5c6)


Documentation

  • New: docs/api/whatsapp-services/README.md — architecture + security model + slash-command reference

  • New: docs/api/whatsapp-services/notify.md — full endpoint reference for POST /api/v1/whatsapp/notify

  • New: collections/openalgo/IN_stock/whatsapp_notify.bru — Bruno collection entry (auto-discovered by /playground)

  • Updated: docs/api/README.md — adds the WhatsApp Services section under the existing service taxonomy

  • Updated: docs/prompt/openalgo python sdk.md — full client.whatsapp(...) reference with all four recipient forms, image/document attachments, fire-and-forget vs synchronous delivery, and inbound slash-command reference

  • 52eb8650docs(mcp): rewrite Remote MCP userguide for traders, drop stale install paths

  • 3b3054bedocs(claude): update CLAUDE.md with new product surfaces, Ruff tooling, and architecture details (#1412)

  • 1a7d3a0adocs(claude): clarify sandbox terminology and split /sandbox vs /analyzer surfaces

  • c7e5f4a9docs(services): align order field names with canonical code (pricetype, product)

  • 83499518 / e2de15ec — Ubuntu Server Installation guide refresh


Install + infrastructure

  • 79557be5feat(install): inline Remote MCP prompt in install-docker.sh and install-multi.sh

  • 5c8b64b9feat(install): prompt to enable Remote MCP during install.sh

  • 04eac147feat(install): simplify single-deploy paths + drop enable-remote-mcp.sh

  • 647183bdfeat(remote-mcp): UI controls for master switch + posture toggles

  • 9ec851abfix(mcp): use HOST_SERVER for SDK loopback so install.sh deploys work

  • 5fa17bd3fix(diagnostics): correct dead secret keys + git info inside Docker (#1388)

  • f786e21achore: add Caddyfile for local https://openalgo.local dev

  • 4e09da8bfix(python-strategy): Stop button works under gunicorn-eventlet (#1404)


Bug fixes (non-WebSocket)

  • a4bdac18fix(angel/api): defensive .get() in place_order response handling (#846)

  • b06ef4a8fix(kotak): align place/modify order payload with official Neo spec (#1398)

  • ca15b333fix(groww/api): support NSE_INDEX/BSE_INDEX in historical (#1338) (#1342)

  • 11778af5fix(search): surface FUT-only MCX underlyings in symbol search (#1385)


Upgrade procedure

For existing installs (Native Ubuntu):

For existing installs (Docker):

For local developers (uv):

Enabling WhatsApp (post-upgrade, optional):

  1. Log in to OpenAlgo, open /whatsapp.

  2. Click Start pairing. A QR code appears.

  3. On your phone: WhatsApp → Settings → Linked devices → Link a device → scan.

  4. Done. The bot auto-starts and reconnects on every server boot from the encrypted session in openalgo.db.

The session blob never leaves your server. There is no second-party service to register with — it's a direct Signal-Protocol connection to WhatsApp.


Contributors

  • @marketcalls (Rajandran) — release management; WhatsApp architecture and full implementation (database schema with Fernet-encrypted session blob + domain-separated salt, dedicated WhatsAppBotThread to satisfy PyO3's unsendable contract, event-bus subscriber wired into all 13 order topics, send-only REST API + session-authed admin blueprint, React /whatsapp page with auto-rotating QR, RUST_LOG suppression for the three known-noisy wars modules, attachment-path allowlist with traversal-token rejection, lazy own-JID capture from is_from_me=True messages, slash-command dispatcher with is_from_me gate, auto-reconnect on app boot); openalgo Python SDK 1.0.50 release with new client.whatsapp(...) API; WebSocket reliability sweep across 14 brokers (subscribe batching, reconnect hardening, fd-leak fixes); new IIFLCapital streaming adapter (#1416, #1430); Groww option chain + WS depth (#1392); Upstox GLOBAL_INDEX world feeds; Zerodha MCX_INDEX wiring (#1385); per-install Fernet salt rotation with crash-safe migration; websocket-proxy fixes (ZMQ bind #1378, mode normalization #1375, request_id correlation #1376, SharedZmqPublisher topology #1374); UI alignment fixes and stale-chunk auto-reload; comprehensive WhatsApp documentation (endpoint reference, SDK prompt-doc, Bruno collection entry).


Links



Last updated