Skip to content
HostEthical
All guides

VPS 15 min read ·

TradingView Webhook to Broker API: Automate Alerts from a VPS

Send TradingView alerts to your broker's API from your own static-IP VPS: webhook rules, a secure Python receiver behind nginx and SSL, alert JSON and testing.

A TradingView alert can't place an API order with an Indian broker on its own. Since 1 April 2026, brokers accept API orders only from a static IP address you have registered, and TradingView sends every webhook from its own servers, whose addresses you can't register as yours. The setup that works is a small receiver on your own static-IP server. TradingView posts the alert to it over HTTPS, and the receiver places the order through your broker's API.

This guide builds that receiver on an Ubuntu 24.04 VPS: a FastAPI app behind nginx with a free Let's Encrypt certificate, a shared secret, duplicate protection and a paper-trading switch. It also covers brokers that accept TradingView alerts themselves, which may save you running a server at all.

Before you start. This is a technical guide for traders automating a strategy they wrote themselves. Nothing here is investment advice, and HostEthical provides servers only, with no strategies, signals or tips. If the static-IP rule is new to you, read our static IP for algo trading guide first.

Why a TradingView alert can't reach your broker directly

Three things stand in the way:

  • The IP isn't yours. TradingView's webhook help page lists the four IPv4 addresses it sends webhooks from: 52.89.214.238, 34.212.75.30, 54.218.53.128 and 52.32.178.7. Every TradingView customer's alerts come from these addresses, and exchange rules let an IP be mapped to only one client, so you can't register them with your broker.
  • Broker APIs need a login header. Kite Connect, for example, expects an Authorization: token api_key:access_token header on every order (docs). A TradingView webhook is just a URL plus the message you type; the only header its help pages mention is the content type.
  • Delivery isn't guaranteed. TradingView says webhooks "may occasionally fail to reach the specified URL", and its page on credentials in webhooks states that its alerts "are not designed for automated trading".

The fix is a bridge that you control:

  1. Your strategy or indicator fires an alert on TradingView.
  2. TradingView posts the alert message to a URL on your VPS, such as https://hook.yourdomain.in/tv-webhook.
  3. Your receiver checks the secret, ignores duplicates, logs the alert and replies at once.
  4. In the background, it places the order through your broker's API. The order leaves from the VPS's registered static IP.

Check your broker's own TradingView options first

Before you build anything, see what your broker already offers. As of October 2026:

  • Dhan says in its help centre that you can run your own TradingView strategy "through webhooks" that Dhan provides for free, connecting your alerts directly to Dhan to place orders.
  • FYERS added TradingView and Chartink webhooks to its Automate tool in April 2026. Its product recap says "your signal generation stays exactly the same while execution runs inside FYERS". The webhook trigger doesn't place orders by itself, so you add an order step to the automation.
  • Dhan, FYERS, Upstox and Angel One have broker pages on TradingView, so you can link your account to TradingView's trading panel and place orders by hand from the chart. That is manual trading, not alert automation.
  • Zerodha has no broker page on TradingView. In a December 2024 TradingQnA thread, Zerodha's reply was that it had "no plans to integrate". Kite uses TradingView charts inside its own platform instead.

We found no built-in alert webhook on the Zerodha, Upstox or Angel One websites.

How the IP rule applies. With a broker's own webhook, the alert lands on the broker's servers and the broker sends the order, so it doesn't come from your IP. Angel One's FAQ on the April 2026 changes says a no-code platform hosted on the broker's servers needs no static IP. Dhan's and FYERS's pages don't say how the rule applies to their webhooks, so confirm with them first.

Which to choose. If your broker's webhook does what you need, it is simpler, with no server to look after. Build your own bridge when you want your own limits and logs, logic the broker's tool can't express, or a broker without a webhook. If you'd rather not write the code, OpenAlgo is an open-source platform you host yourself. Per its TradingView docs, it generates the alert message for you and checks a strategy alert's position size against your real position.

A static IP for your webhook bridge. Algo VPS Mumbai has its own dedicated IPv4 address, Python with the broker SDKs preinstalled, and a ready systemd template for your receiver. See Algo VPS →

TradingView webhook rules your receiver must meet

These come from TradingView's help centre and pricing page, as of October 2026:

RuleWhat TradingView saysWhat it means for you
PlanWebhook notifications are included in Essential, Plus, Premium and Ultimate, not the free Basic plan (pricing)You need a paid plan
2FAWebhook alerts are allowed only when two-factor authentication is onTurn on 2FA first
PortsOnly 80 and 443; other ports are rejected (source)Put nginx in front of your app
IPv6Not supported for webhooksPoint the hostname at your IPv4 address
TimeoutThe request is cancelled if your server takes longer than three secondsReply at once and trade in the background
Content typeapplication/json if the message is valid JSON, otherwise text/plainWrite the message as JSON
ResendsAfter a 5xx reply (except 504), the alert is sent again after 5 seconds, up to 3 times (source)Expect duplicates and ignore them
Redirects3xx replies count as errors (source)Enter the exact https:// URL
Source IPsThe four addresses listed aboveYou can allow only these in nginx

Over HTTPS, TradingView also presents a client certificate for webhook-server@tradingview.com (details). Its page doesn't name the authority that issues it, so a check on the name alone proves little. This guide relies on the secret and the IP list instead.

Step 1: subdomain, firewall, nginx and a free certificate

Run algo-ip on the server and create an A record, for example hook.yourdomain.in, pointing at the IPv4 address it prints. Our DNS records guide explains A records. You don't need an AAAA record, since TradingView doesn't use IPv6.

Algo VPS ships with only SSH open in its firewall. Install nginx and Certbot using the snap that Certbot's instructions recommend, then open ports 80 and 443. Port 80 has to stay open so that Let's Encrypt can renew the certificate.

sudo apt update && sudo apt install -y nginx snapd
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
sudo ufw allow 'Nginx Full'          # opens 80 and 443; SSH stays open

Save this as /etc/nginx/sites-available/tvhook, with your own hostname:

server {
    listen 80;
    server_name hook.yourdomain.in;

    location = /tv-webhook {
        # TradingView's webhook addresses; check its help page if they change
        allow 52.89.214.238;
        allow 34.212.75.30;
        allow 54.218.53.128;
        allow 52.32.178.7;
        deny all;

        client_max_body_size 16k;
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 5s;
    }

    location / {
        return 404;
    }
}

Enable it, then let Certbot fetch a certificate and switch the site to HTTPS:

sudo ln -s /etc/nginx/sites-available/tvhook /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d hook.yourdomain.in
sudo certbot renew --dry-run         # checks that automatic renewal works

The allow lines let only TradingView's four addresses through; everyone else gets a 403 error. If alerts start failing, look for 403 entries in /var/log/nginx/access.log and check TradingView's list for changes. Don't put a proxy such as Cloudflare in front of this hostname, or nginx sees the proxy's addresses instead. More on certificates in what an SSL certificate is.

Step 2: a minimal webhook receiver in Python

Install FastAPI and Uvicorn into the server's Python environment (FastAPI 0.146 and Uvicorn 0.54 were current on PyPI in October 2026), then generate a long random secret:

sudo /opt/algo/venv/bin/pip install fastapi uvicorn
python3 -c "import secrets; print(secrets.token_urlsafe(32))"

Add your settings to /opt/algo/.env, which only root can read:

TV_WEBHOOK_SECRET=paste-the-string-you-just-generated
TV_LIVE=0
KITE_API_KEY=your_kite_api_key
KITE_TOKEN_FILE=/var/lib/tvhook/kite_token.json

Save the receiver as /opt/algo/tvhook.py:

# /opt/algo/tvhook.py: receive TradingView alerts, place orders in the background
import hashlib
import hmac
import json
import logging
import os
import sqlite3

import uvicorn
from fastapi import BackgroundTasks, FastAPI, Request
from fastapi.responses import JSONResponse

SECRET = os.environ["TV_WEBHOOK_SECRET"].encode()
LIVE = os.environ.get("TV_LIVE") == "1"        # paper mode unless TV_LIVE=1
ALLOWED = {"NSE:INFY", "NSE:SBIN"}             # the only symbols this receiver may trade
MAX_QTY = 10                                   # hard cap per order
DB_PATH = os.path.join(os.environ.get("STATE_DIRECTORY", "/opt/algo"), "seen.db")

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
log = logging.getLogger("tvhook")

db = sqlite3.connect(DB_PATH, check_same_thread=False)
db.execute("CREATE TABLE IF NOT EXISTS seen (id TEXT PRIMARY KEY, at TEXT DEFAULT CURRENT_TIMESTAMP)")
db.commit()

app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)


@app.post("/tv-webhook")
async def tv_webhook(request: Request, tasks: BackgroundTasks):
    raw = await request.body()
    ip = request.client.host if request.client else "unknown"
    try:
        alert = json.loads(raw)
    except ValueError:
        log.warning("rejected: not JSON, from %s", ip)
        return JSONResponse({"ok": False}, status_code=400)

    given = str(alert.pop("secret", "")).encode() if isinstance(alert, dict) else b""
    if not hmac.compare_digest(given, SECRET):
        log.warning("rejected: wrong secret, from %s", ip)
        return JSONResponse({"ok": False}, status_code=401)

    key = hashlib.sha256(raw).hexdigest()[:20]
    cur = db.execute("INSERT OR IGNORE INTO seen (id) VALUES (?)", (key,))
    db.commit()
    if cur.rowcount == 0:
        log.info("%s duplicate, ignored", key)
        return {"ok": True, "duplicate": True}

    log.info("%s accepted from %s: %s", key, ip, alert)
    tasks.add_task(handle, alert, key)
    return {"ok": True}


def handle(alert, key):
    try:
        symbol = f"{alert['exchange']}:{alert['symbol']}".upper()
        side = str(alert["action"]).upper()
        qty = int(float(alert["qty"]))
        price = float(alert["price"])
        if symbol not in ALLOWED or side not in ("BUY", "SELL") or not 0 < qty <= MAX_QTY:
            log.error("%s outside limits, not traded: %s", key, alert)
            return
        if not LIVE:
            log.info("%s PAPER %s %s x %d at %s", key, side, symbol, qty, price)
            return
        order_id = place_order(symbol, side, qty, price, tag=key)
        log.info("%s order placed: %s", key, order_id)
    except Exception:
        log.exception("%s order failed, check your order book", key)


def place_order(symbol, side, qty, price, tag):
    """Send one order to your broker and return its order ID (see Step 3)."""
    raise NotImplementedError


if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)

Here is what it does, in order:

  • Checks the secret. Anything that isn't JSON, or lacks the right secret, is rejected. The comparison takes the same time whether a guess is close or not, so the secret can't be worked out character by character.
  • Ignores duplicates. It stores a hash of each raw message in SQLite. A TradingView resend carries the same message, so the second copy is logged and dropped, even after a restart.
  • Replies fast. The order goes into a FastAPI background task, which runs after the response is sent, well inside TradingView's three seconds.
  • Enforces limits. It trades only symbols in ALLOWED and at most MAX_QTY, so a typo in an alert can't send an order for 1,000 shares.
  • Starts in paper mode. Until TV_LIVE=1, it only logs what it would have done.
  • Never retries an order. If the broker call fails, the order may or may not exist, so check your order book rather than sending it again.

Uvicorn listens on 127.0.0.1 only, so nothing outside the server can reach it except through nginx. By default it trusts the X-Forwarded-For header from a proxy on 127.0.0.1, so your logs show the sender's real address.

Step 3: place the order (a stub and a Kite Connect example)

The receiver calls place_order(symbol, side, qty, price, tag) and expects an order ID back. Keep everything broker-specific inside it, so changing brokers means rewriting one function. For Zerodha, the kiteconnect SDK (version 5.2.2 on PyPI as of October 2026) is preinstalled on Algo VPS. Replace the stub with:

from kiteconnect import KiteConnect   # put this with the other imports


def place_order(symbol, side, qty, price, tag):
    exchange, tradingsymbol = symbol.split(":", 1)
    with open(os.environ["KITE_TOKEN_FILE"]) as f:    # written by your morning login
        access_token = json.load(f)["access_token"]
    kite = KiteConnect(api_key=os.environ["KITE_API_KEY"], access_token=access_token)
    return kite.place_order(
        variety=kite.VARIETY_REGULAR,
        exchange=exchange,
        tradingsymbol=tradingsymbol,
        transaction_type=side,            # "BUY" or "SELL"
        quantity=qty,
        product=kite.PRODUCT_MIS,         # intraday; kite.PRODUCT_CNC for delivery
        order_type=kite.ORDER_TYPE_LIMIT,
        price=price,
        validity=kite.VALIDITY_DAY,
        tag=tag,                          # shows in your order book
    )
  • It sends a limit order at the alert's price. Market orders sent through APIs now come with extra rules. Kite Connect's order docs describe a market_protection setting for MARKET and SL-M orders, and Angel One's FAQ on the April 2026 changes says market and IOC orders are prohibited for algo trading. Check your broker's rules before you change the order type.
  • It reads the access token at order time. Kite tokens expire daily, so a token read at start-up would fail the next morning. The login helper in our Kite Connect guide saves the day's token as a small JSON file: point its TOKEN_FILE at /var/lib/tvhook/kite_token.json.
  • Symbols need checking. TradingView's ticker for an NSE share such as INFY is the same as Zerodha's tradingsymbol, but futures and options are named differently, so map them yourself.
  • Other brokers. For Dhan, the function would call dhan.place_order() from the dhanhq SDK, which identifies instruments by a numeric security ID rather than a symbol. See our Dhan API guide.

Step 4: run it as a service

Algo VPS includes a systemd template, algo@, that runs /opt/algo/<name>.py, restarts it if it crashes, starts it on boot and loads /opt/algo/.env. The template runs bots as root, which is more power than a receiver facing the internet needs, so give this one its own user:

sudo useradd --system --shell /usr/sbin/nologin tvhook
sudo systemctl edit algo@tvhook

In the editor that opens, add these lines and save:

[Service]
User=tvhook
StateDirectory=tvhook

Then start it and watch the log:

sudo systemctl enable --now algo@tvhook
journalctl -u algo@tvhook -f

systemd still reads /opt/algo/.env as root before switching user. StateDirectory creates /var/lib/tvhook, owned by tvhook, for the duplicate log and the day's access token. After each morning login, run sudo chown tvhook /var/lib/tvhook/kite_token.json so the receiver can read it; the file stays chmod 600. For timers, daily re-login, health checks and Telegram alerts, see running a trading bot 24/7.

Step 5: write the alert and paper-test it

In TradingView, create the alert, turn on Webhook URL in its notification settings and enter https://hook.yourdomain.in/tv-webhook. Paste this as the message, with your own secret:

{
  "secret": "paste-your-TV_WEBHOOK_SECRET",
  "exchange": "NSE",
  "symbol": "{{ticker}}",
  "action": "{{strategy.order.action}}",
  "qty": "{{strategy.order.contracts}}",
  "price": "{{close}}",
  "order": "{{strategy.order.id}}",
  "fired_at": "{{timenow}}"
}

TradingView fills in the placeholders when the alert fires: {{ticker}} becomes the symbol (INFY), {{strategy.order.action}} "buy" or "sell", {{strategy.order.contracts}} the quantity, {{close}} the bar's closing price and {{timenow}} the firing time. The strategy placeholders work only in strategy alerts; for an indicator alert, type the action and quantity yourself. The firing time and order ID make each genuine alert unique, so only true resends look like duplicates.

Test in this order, with TV_LIVE=0:

  1. On the server, send a fake alert straight to Uvicorn:
    curl -s http://127.0.0.1:8000/tv-webhook -H 'Content-Type: application/json' \
      -d '{"secret":"paste-your-TV_WEBHOOK_SECRET","exchange":"NSE","symbol":"INFY","action":"buy","qty":"1","price":"1500.5","fired_at":"2026-10-19T04:00:00Z"}'
    The log should show a PAPER line. Send the same request again and you should see "duplicate". With a wrong secret, you should get a 401 error.
  2. From your laptop, send the same request to the https:// address. nginx should answer with a 403 error, which proves the IP allowlist works.
  3. Let a real alert fire. Check the Webhook status column in TradingView's alert log and the PAPER line in journalctl.
  4. Keep it in paper mode for a few sessions and compare the logged trades with the strategy's trades on the chart.
  5. To go live, set TV_LIVE=1 in /opt/algo/.env, start with the smallest quantity and run sudo systemctl restart algo@tvhook.

Running it safely once it's live

  • Treat the secret as a webhook key, not a password. TradingView warns against putting logins or passwords in webhook messages. Never put broker API keys, passwords or TOTP secrets in an alert. The webhook secret can only do what your receiver allows (listed symbols, capped quantity), and you can change it in .env and in your alerts at any time.
  • Reconcile every day. A missed webhook leaves TradingView's strategy and your account out of step. Compare your positions with the strategy daily, and stop the bot if they differ.
  • Keep a kill switch. Setting TV_LIVE=0 and restarting turns trading off but keeps the logs coming. sudo systemctl stop algo@tvhook stops everything. nginx then returns a 502 error, which TradingView resends three times at 5-second intervals, so wait a minute before you start it again.
  • Stay within the rules. Below 10 orders per second you don't register the algo with the exchange. The static-IP route is meant for your own strategies, so if you're unsure how your broker treats signals generated on TradingView, ask its API desk. Running a strategy for someone else, or offering this bridge to others, brings in SEBI's rules for algo providers: see SEBI algo trading rules.
  • Keep the server patched. Algo VPS installs security updates automatically, and Certbot renews the certificate by itself.

What it costs. Algo VPS Mumbai is ₹1,510 a month, or about ₹1,325 a month when billed yearly, with a dedicated IPv4 address, Python with the broker SDKs, the algo@ service template and IPv4-first networking set up at first boot. A plain Cloud VPS from ₹860 a month, or about ₹774.17 a month when billed yearly works too if you're happy to do the setup yourself; Mumbai adds a monthly location fee. And if your broker's own webhook covers your needs, you may not need a server at all.

Run your TradingView bridge from one fixed IP. A Linux VPS in Mumbai with a dedicated IPv4 address, set up for Python trading bots and billed in rupees. See Algo VPS →

Frequently asked questions

Can a TradingView alert place orders with Zerodha or Dhan directly?

Not through your own API key. Since 1 April 2026, Indian brokers accept API orders only from a static IP you have registered, and TradingView sends webhooks from its own four shared addresses. You need a small receiver on your own static-IP server that turns each alert into a broker API call, or a broker feature that accepts TradingView webhooks itself, such as Dhan's webhooks or FYERS Automate.

Which TradingView plan do I need for webhooks?

Any paid plan. As of October 2026, TradingView's pricing page lists webhook notifications on the Essential, Plus, Premium and Ultimate plans, but not on the free Basic plan. You also need two-factor authentication switched on in your TradingView account, because TradingView allows webhook alerts only when 2FA is enabled.

Why does my TradingView webhook fail or time out?

Usually because the receiver is too slow, on the wrong port or redirecting. TradingView cancels a request after three seconds, accepts only ports 80 and 443, doesn't support IPv6 and treats redirects as errors. Reply immediately and place the order in the background, put nginx in front on port 443, point the hostname at an IPv4 address and paste the exact https:// URL. The Webhook status column in the alert log shows the error.

How do I stop TradingView webhooks from placing duplicate orders?

Keep a fingerprint of every alert and ignore repeats. When your server replies with a 5xx error other than 504, TradingView sends the same message again after 5 seconds, up to three times. Include the firing time (the timenow placeholder) in the message so genuine alerts always differ, then hash each message and store the hashes in a small database, so a resend is skipped even after a restart.