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 Tradeboard admin UI with a QR scan; the encrypted session blob is then stored in tradeboard.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 tradeboard 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 (tradeboard)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 Tradeboard 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 → Tradeboard 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_blobintradeboard.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 Tradeboard 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:<tradeboard>/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 Tradeboard 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
tradeboard 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/tradeboard/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 Tradeboard 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<tradeboard>/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 (
tradeboard)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.6tradeboard==1.0.49→tradeboard==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.tradeboardSDK pin:1.0.49→1.0.50(PyPI: https://pypi.org/project/tradeboard/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/tradeboard/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/tradeboard 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://tradeboard.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/tradeboard-flask/<deploy-name>/tradeboard
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/tradeboard/<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 Tradeboard, 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
tradeboard.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); tradeboard 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/wesoftcorp/tradeboard-docs
- Documentation: https://docs.algo.wesoftcorp.com
- Python SDK on PyPI: https://pypi.org/project/tradeboard/1.0.50/
- WhatsApp service docs: https://docs.algo.wesoftcorp.com/api-documentation/v1/whatsapp-services
- Discord: https://www.tradeboard.in/discord
- Issue tracker: https://github.com/wesoftcorp/tradeboard-docs/issues
