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
/whatsappadmin page with auto-rotating QR pair flow, a single trader-facingPOST /api/v1/whatsapp/notifyendpoint that accepts text + image + document attachments, a dedicated worker thread that satisfies wars's PyO3 unsendable contract, and asubscribers/whatsapp_subscriberthat 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 ownis_from_me=Truemark — 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=Trueby 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_HOSTinstead of all interfaces (#1378), normalize subscribe/unsubscribe mode case-insensitively (#1375), correlate ack via request_id (#1376), route cache invalidation throughSharedZmqPublisherto eliminate the PUB→PUB topology (#1374).Three new broker integrations — IIFLCapital 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 salt —
FERNET_SALTenv var is now provisioned per install (32-byte hex, generated byutils/env_check.py) and feeds the Fernet KDF indatabase/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 bump —
2.0.1.0→2.0.1.1. SDK pin (openalgo)1.0.49→1.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_subscriber → WhatsAppBotThread → wars.send(); incoming slash-commands flow wars.on_message → is_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 anon_qrcallback that streamswhatsapp_qrSocketIO events with adata:image/pngURL, and waits onwait_until_ready(timeout=300)for the phone-side scanQR refreshes ~every 30 seconds; React
/whatsapppage swaps the<img>source on eachwhatsapp_qrevent without pollingOn pair success:
export_session()→ Fernet-encrypted → persisted towhatsapp_config.session_blobinopenalgo.db→ temp file unlinkedPair-code path (
POST /whatsapp/pairwithphoneparameter) for users who prefer not to scan a QR
Encryption at rest — database/whatsapp_db.py:
Fernet key derived via PBKDF2-SHA256(
API_KEY_PEPPER,FERNET_SALT + b":whatsapp-session", 100k iters), 32-byte output, base64-urlsafe encodedDomain 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 othersIdempotent SQLite
ALTER TABLE ADD COLUMNmigration runs at every init so existing installs pick up theowner_user_id/owner_usernamecolumns without manual schema work
The unsendable PyO3 trap — c44a420b:
wars.WhatsAppis#[pyclass(unsendable)]— every method call panics if invoked from a thread other than its creatorSolution: a dedicated
WhatsAppBotThreadowns the wars instance for its lifetime; request threads enqueue(op, args, result_holder, event)on aqueue.Queueand wait on athreading.Eventfor the worker to dispatchwars.send()Re-entrant: command handlers (which wars dispatches on the bot thread itself) bypass the queue via a
threading.get_ident() == self._bot_thread_idcheck so they don't deadlock on themselvesSame shape Telegram uses for python-telegram-bot, for the same reason
REST surface — intentionally narrow — restx_api/whatsapp_bot.py:
POST /api/v1/whatsapp/notifyis the only public endpointPairing, start/stop, config, users, broadcast, stats, preferences live behind the session-authed
/whatsapp/*blueprint — admin onlyA 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."ifis_ready()is false; we explicitly do not queue when unpaired
Send paths — one unified call:
client.whatsapp("Build #482 deployed")→ wars's single-argsend("text")form, routes to the paired device's own number (no need to know own JID)client.whatsapp("hi", to="919876543210")→ single recipientclient.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 captionclient.whatsapp("report", username="alice", document="/srv/reports/eod.pdf", filename="EOD.pdf")→ documentAttachment paths validated against
WHATSAPP_ATTACHMENT_ROOTSallowlist (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 with400 image_path is not allowed
Inbound commands — services/whatsapp_bot_service.py:
/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 boot — app.py:_autostart_whatsapp_bot:
Background thread spawned during
_init_databases_and_schedulers(afterdb_ready.set())Loads the encrypted session blob, calls
wars.WhatsApp.from_bytes(blob), starts the worker thread, registers handlersNo QR scan on restart —
is_ready()flips to true within ~1s of bootIf 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
Frontend — frontend/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_statusNew
MessageCircleicon in the profile dropdown so WhatsApp visually differs from Telegram'sMessageSquare
RUST_LOG quieting — services/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.envfor diagnostics
openalgo Python SDK 1.0.50 — released to PyPI alongside this version:
New
WhatsAppAPImix-in addsclient.whatsapp(...)with the four recipient forms and image/document payloadsMirrors the
client.telegram()ergonomics for traders already familiar with the SDKAvailable at https://pypi.org/project/openalgo/1.0.50/
WebSocket reliability sweep — 14 brokers touched
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 interfaces6ddff2bb(#1375) —fix(websocket_proxy): normalize subscribe/unsubscribe mode case-insensitively9a0f5e42(#1376) —fix(websocket_client): correlate subscribe/unsubscribe acks via request_id521ea129(#1374) —fix(cache_invalidation): route through SharedZmqPublisher to eliminate PUB->PUB topology25eed728—fix(ui/websocket-test): keep LTP card live when Depth is also subscribedf8a1ff4f(#1386) —chore(websocket): remove dead get_supported_brokers_list()06d4cb34—docs(audit): add WebSocket broker priority audit
New broker integrations + exchange expansion
0ad69cf3/15179371— IIFLCapital websockets integrated + broker hardening + option-chain perf440b78ef— Groww full option chain + websocket depth integrationc0335582— Upstox GLOBAL_INDEX world feeds (US30 / JAPAN225 / HANGSENG, plus IFSC-routed GIFTNIFTY) on indices and indicatorscd4095eb— Zerodha MCX_INDEX wired through quote/history/depth/wsca15b333— 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.
2f2e9bee—fix(security): rotate Fernet to per-install salt with crash-safe auto-migratione3c5285d— placeFERNET_SALTadjacent toAPI_KEY_PEPPERin.env, handle the 4 file-state cases cleanly (placeholder / valid / missing / mid-migration)a1455ff1— atomic.envrewrite falls back to in-place on Docker bind mounts84922078— degrade gracefully when.envis unwritable + pin Docker UID 1000 (#1394)2981ff52— post-FERNET_SALT cleanup — security perm-check, dedupe, pool invalidate (#1394)b9301b78— never recommend rotatingAPI_KEY_PEPPERon a populated DB (carried in from late v2.0.0.9 work, re-asserted here)
What this means operationally:
New installs:
utils/env_check.pygenerates a 32-byte hexFERNET_SALT, writes it adjacent toAPI_KEY_PEPPERin.env, and uses it directly. No migration runs.Existing installs upgrading to 2.0.1.1: on first boot,
env_checkdetects 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 persistedFERNET_SALTlets the next boot resume cleanly.Same salt entropy feeds the new WhatsApp session blob via the
:whatsapp-sessiondomain separator.
UI + frontend
cd692653—feat(orderbook): make Quantity editable in Modify Order dialogf968b403—fix(ui): align table content with stats cards on Positions/Orderbook/Holdings/Tradebookd3aa8b6b—fix(frontend): auto-reload on stale-chunk import failure (#1393)— fixes the "ChunkLoadError" trader sees after a deploy when the browser is still holding the oldindex-*.jsreferenced63ec927— drop hero gradient on the home page, use solidtext-primary3f4e2b2b— home: "New in V2 — 12-Tool Options Analytics Suite" pill624f8726— home: Integrates With + Made for AI sections61370758/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 toAPI_KEY_PEPPERNo new keys for WhatsApp —
WHATSAPP_KEY_SALTreuses the existingFERNET_SALTwith a:whatsapp-sessiondomain 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.3dependency (also added torequirements.txt+requirements-nginx.txt)SDK pin (
openalgo)1.0.49→1.0.50
utils/version.py:
VERSION = "2.0.1.1"
requirements.txt + requirements-nginx.txt:
wars==0.1.3added afterpython-telegram-bot==22.6openalgo==1.0.49→openalgo==1.0.50
frontend/src/stores/alertStore.ts:
New
whatsapptoast category alongsidetelegram
Database schema
New tables created by database/whatsapp_db.py on first boot:
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.3added — PyO3 binding over the whatsapp-rust crate; provides the WhatsApp Web client. Wheels available for Python 3.12+ via abi3.openalgoSDK pin:1.0.49→1.0.50(PyPI: https://pypi.org/project/openalgo/1.0.50/)urllib32.6.3→2.7.0(4519f55f) — clears 8 Dependabot high-severity alertsaxios1.15→1.16,python-multipart0.0.26→0.0.27, pinpip>=26.1(6d61e5c6)
Documentation
New:
docs/api/whatsapp-services/README.md— architecture + security model + slash-command referenceNew:
docs/api/whatsapp-services/notify.md— full endpoint reference forPOST /api/v1/whatsapp/notifyNew:
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 taxonomyUpdated:
docs/prompt/openalgo python sdk.md— fullclient.whatsapp(...)reference with all four recipient forms, image/document attachments, fire-and-forget vs synchronous delivery, and inbound slash-command reference52eb8650—docs(mcp): rewrite Remote MCP userguide for traders, drop stale install paths3b3054be—docs(claude): update CLAUDE.md with new product surfaces, Ruff tooling, and architecture details (#1412)1a7d3a0a—docs(claude): clarify sandbox terminology and split /sandbox vs /analyzer surfacesc7e5f4a9—docs(services): align order field names with canonical code (pricetype, product)83499518/e2de15ec— Ubuntu Server Installation guide refresh
Install + infrastructure
79557be5—feat(install): inline Remote MCP prompt in install-docker.sh and install-multi.sh5c8b64b9—feat(install): prompt to enable Remote MCP during install.sh04eac147—feat(install): simplify single-deploy paths + drop enable-remote-mcp.sh647183bd—feat(remote-mcp): UI controls for master switch + posture toggles9ec851ab—fix(mcp): use HOST_SERVER for SDK loopback so install.sh deploys work5fa17bd3—fix(diagnostics): correct dead secret keys + git info inside Docker (#1388)f786e21a—chore: add Caddyfile for local https://openalgo.local dev4e09da8b—fix(python-strategy): Stop button works under gunicorn-eventlet (#1404)
Bug fixes (non-WebSocket)
a4bdac18—fix(angel/api): defensive .get() in place_order response handling (#846)b06ef4a8—fix(kotak): align place/modify order payload with official Neo spec (#1398)ca15b333—fix(groww/api): support NSE_INDEX/BSE_INDEX in historical (#1338) (#1342)11778af5—fix(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):
Log in to OpenAlgo, open
/whatsapp.Click Start pairing. A QR code appears.
On your phone: WhatsApp → Settings → Linked devices → Link a device → scan.
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
WhatsAppBotThreadto satisfy PyO3's unsendable contract, event-bus subscriber wired into all 13 order topics, send-only REST API + session-authed admin blueprint, React/whatsapppage 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 fromis_from_me=Truemessages, slash-command dispatcher withis_from_megate, auto-reconnect on app boot); openalgo Python SDK 1.0.50 release with newclient.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
Repository: https://github.com/marketcalls/openalgo
Documentation: https://docs.openalgo.in
Python SDK on PyPI: https://pypi.org/project/openalgo/1.0.50/
WhatsApp service docs: https://docs.openalgo.in/api-documentation/v1/whatsapp-services
Discord: https://www.openalgo.in/discord
YouTube: https://www.youtube.com/@openalgo
Issue tracker: https://github.com/marketcalls/openalgo/issues
Last updated