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 scan- QR refreshes ~every 30 seconds; React
/whatsapppage swaps the<img>source on eachwhatsapp_qrevent without polling - On pair success:
export_session()→ Fernet-encrypted → persisted towhatsapp_config.session_blobinopenalgo.db→ temp file unlinked - Pair-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 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 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 creator- Solution: 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 themselves - Same 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 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."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")→ document- Attachment 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:
| 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 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 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
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_status - New
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 payloads - Mirrors the
client.telegram()ergonomics for traders already familiar with the SDK - Available at https://pypi.org/project/openalgo/1.0.50/
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 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_PEPPER- No 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:
| 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.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 reference - New:
docs/api/whatsapp-services/notify.md— full endpoint reference forPOST /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— fullclient.whatsapp(...)reference with all four recipient forms, image/document attachments, fire-and-forget vs synchronous delivery, and inbound slash-command reference 52eb8650—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):
cd /var/python/openalgo-flask/<deploy-name>/openalgo
sudo ./install/update.sh
# update.sh runs migrate_all.py — the new whatsapp_config / whatsapp_users
# tables and the owner_user_id column are created automatically. The
# FERNET_SALT migration runs on first boot inside env_check; existing
# broker-auth ciphertext is re-encrypted on the fly.For existing installs (Docker):
cd /opt/openalgo/<domain>
sudo docker compose pull
sudo docker compose up -d
# The container's start.sh runs migrate_all.py before gunicorn boots.
# FERNET_SALT migration runs on first start.For local developers (uv):
git pull origin main
uv sync
cd frontend && npm install && npm run build
uv run app.pyEnabling 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
Agent Instructions: Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter:
GET https://docs.openalgo.in/change-log/release/version-2.0.1.1-released.md?ask=<question>
The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.