github marketcalls/openalgo openalgo-iiflcapital-whatsapp
v2.0.1.1

3 hours ago

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
  • 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 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):

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.py

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



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.

Don't miss a new openalgo release

NewReleases is sending notifications on new releases.