How TradingView webhook alerts work

What TradingView posts when a webhook alert fires: placeholders, JSON rules, the 3 second timeout, secrets in the URL, and the failures that break a bridge.

Updated 30 Aug 2026

A TradingView alert can do two separate things. It can notify you, through the app, email or a popup, and it can send an HTTP request to a URL you choose. The second thing is the webhook. If you want a Pine strategy to reach anything outside TradingView, the webhook is the only outbound channel there is, so its rules are worth knowing exactly.

Alert versus webhook

An alert is the scheduled job on TradingView's servers. It watches a condition (a price line, an indicator plot, an alert() call or a strategy order fill) and fires when the condition is met. The webhook is one of its delivery options: a field in the alert dialog where you paste a URL. When the alert fires, TradingView sends a POST request to that URL with the alert message as the request body (TradingView help: How to configure webhook alerts).

That framing matters for two reasons. First, the message body is written by you, in the alert dialog or in Pine, and TradingView does not wrap it in anything. What you type is what arrives. Second, the webhook is fire and forget. TradingView does not read the response body, does not follow a conversation, and has no idea what your server did with the message.

The message body and placeholders

The body is plain text unless you make it JSON. TradingView substitutes a set of double brace placeholders before sending. The ones that matter for an order message are {{ticker}}, {{exchange}}, {{close}}, {{open}}, {{high}}, {{low}}, {{volume}}, {{time}}, {{timenow}} and {{interval}}. Strategy alerts add {{strategy.order.action}}, {{strategy.order.contracts}}, {{strategy.order.price}}, {{strategy.order.id}} and {{strategy.order.alert_message}} (TradingView help: How to use a variable value in alert).

Three details from that page decide whether your JSON survives:

  • Price placeholders expand to fixed point numbers with a decimal point and no grouping, so {{close}} is safe inside JSON without quotes.
  • {{time}} is the bar time and {{timenow}} is the fire time. Both are UTC in the form 2019-08-27T09:56:00Z. Quote them as strings.
  • {{strategy.order.alert_message}} is the only placeholder that carries a string you built in Pine at runtime. If your strategy order has no alert_message, the placeholder is sent unexpanded, literally as {{strategy.order.alert_message}}.

JSON body rules

TradingView checks the body once, at send time. If the whole body parses as JSON the request goes out with Content-Type: application/json. Otherwise it goes out as text/plain (TradingView help: webhooks). Nothing is repaired on the way.

The usual way to lose that check is numbers. Pine's str.format() groups thousands by default, so a stop at 57400 becomes 57,400, which is not a JSON number. Use the explicit number pattern wherever a value enters the body:

alertMsg = '{"sym":"{{ticker}}",' +     str.format('"side":"{0}","type":"{1}",', side, stratType) +     '"price":{{close}},' +     str.format('"sl":{0,number,#.##},"tp":{1,number,#.##},"qty":{2,number,#}',                slLvl, tpLvl, qty) +     ',"time":"{{timenow}}"}'

Note that the TradingView placeholders sit outside str.format(). Pine treats a lone { inside the format string as the start of a placeholder of its own, and the doubled braces raise an unbalanced bracket error at runtime. Concatenate them around the formatted parts instead.

Once per bar close

A webhook fires as often as the alert fires. For a strategy that is once per bar close by default. For an alert() call in an indicator the default is once per bar, which means the first tick that satisfies the condition, before the bar is confirmed (Pine Script docs: Alerts). Pass alert.freq_once_per_bar_close unless you have a reason not to, and gate the condition on barstate.isconfirmed. The alert() versus alertcondition() page walks through the combinations.

Timeouts, ports, retries

The receiving side has hard limits (TradingView help: webhooks):

RuleWhat it means for a bridge
Three second timeoutAcknowledge first, do the slow work after the response.
Ports 80 and 443 onlyA URL on port 8080 or 3000 is rejected.
No IPv6The hostname needs an A record.
2FA required on the accountWebhook alerts are disabled until it is on.
Delivery "may occasionally fail"Look at the Webhook status column in the alert log.

The help page does not describe a retry schedule. Design as if there is none: if your server was down for those three seconds, that signal is gone, and the only record is the alert log.

Secrets in the URL

A webhook URL is a bearer credential. Anyone who has it can post a signal to your account, and TradingView adds no signature header you could verify. So the URL itself has to be unguessable and rotatable, and it must never appear in the alert message, in a published script or in a screenshot. If it leaks, rotate it and update the alert.

Testing with a request bin

Before pointing an alert at real infrastructure, point it at a disposable request inspector such as webhook.site, which TradingView's own help page uses in its example. Create the alert, fire it with a loose condition (say, price crossing a level it already crossed), and read the raw request: the content type header, the exact body, and whether every placeholder expanded.

Then replay the same body at your real endpoint with curl so you can separate "TradingView sent something odd" from "my server did something odd":

{  "sym": "BANKNIFTY",  "side": "BUY",  "type": "ORB",  "price": 57530,  "sl": 57400,  "tp": 57790,  "qty": 30,  "time": "2026-08-27T09:20:00+05:30"}

Sarathi's payload, as a worked example

That body is the shape Sarathi's parser expects. The field order is sym, side, type, price, sl, tp, qty, time. Only sym and side are required; the rest fill in the order card when present. The parser is lenient about names: symbol or ticker work for sym, action for side, quantity or size for qty, stop or stoploss for sl, target or takeprofit for tp, and close or entry for price. Side accepts BUY, SELL, LONG and SHORT.

Symbols are normalised: an NSE:, BSE: or MCX: prefix is dropped, the trailing digit and exclamation mark that TradingView appends to continuous futures symbols is dropped, and GOLD and SILVER map to the mini contracts GOLDM and SILVERM. A plain text body in the form BUY BANKNIFTY 30 sl=57400 tp=57790 is also accepted, for alerts written by hand in the dialog.

Deduplication uses id if you send one, otherwise the combination of symbol, side, type and time. Two posts with the same key produce one order card, which is why the time field is worth including even though the parser does not require it.

The endpoint answers inside TradingView's window by acknowledging first and delivering the card afterwards. Responses are small JSON objects. The ones a TradingView-side mistake produces:

ResponseCause
200 {"ok":true,"ignored":"…"}Body was an unexpanded placeholder, such as an exit with no alert_message
400 {"error":"malformed JSON body"}Body starts with { but does not parse
400 {"error":"<field> must be a JSON number, got string"}A numeric field was sent quoted or as NaN/Infinity
400 {"error":"symbol not in universe"}Symbol outside the supported instrument list

The full response table is on the webhooks page and the contract on the developers page.

Common failures

Thousands separators. "sl":57,400 breaks the JSON and the request arrives as text/plain. Because the body still starts with {, the parser tries JSON, fails, and answers 400 malformed JSON body. "sl":"57400" in quotes is valid JSON but is rejected with 400 {"error":"sl must be a JSON number, got string"}; every numeric field (price, sl, tp, qty and their aliases) must be a bare JSON number. Use {0,number,#.##}.

Wrong content type. A body that is meant to be JSON but arrives as text/plain almost always means a JSON syntax slip: a trailing comma, a smart quote pasted from a document, or a comment. Paste the body into any JSON validator.

Unexpanded placeholders. A strategy alert set to fire on order fills sends {{strategy.order.alert_message}} for every fill, including exits that were placed without an alert_message argument. Sarathi ignores those rather than erroring, but your own bridge needs the same guard.

Expired alerts. Alerts carry an expiry date set in the dialog. When it passes the alert simply stops, and nothing tells the receiver. If signals stop arriving on a quiet day, check the Alerts panel before checking the server.

Stale snapshots. An alert is a snapshot of the script and its inputs at creation time (Pine Script docs: Alerts). Editing the script does not update the running alert. Delete it and create it again.

Try it in Sarathi

Sarathi is the receiving end of this page. Paste your per-user webhook URL into a TradingView alert, and each valid post becomes an order card in Telegram and on the web dashboard, where you tap Confirm at ½, 1× or 2× size, or Skip. Nothing is placed without that tap, and a card that is not answered within 15 minutes expires. Orders route to a paper broker today; Dhan and Zerodha adapters are planned. Planned The Studio writes Pine v6 strategies with this exact payload already wired into alert_message and alert(). Open the Studio.

Questions

Sarathi writes and checks Pine v6 strategies; paper trade before you connect a broker.

Open the Studio
Sarathiसारथी

Sarathi means charioteer. The charioteer drives; the warrior decides when to shoot. Here the software drives, and you confirm every order.

Status

Pre-launch · pre-revenue
Bridge verified live 24 Aug 2026

Get started →

Sarathi is an execution bridge, not an investment adviser, and is not registered with SEBI as a Research Analyst. Nothing on this page is advice or a recommendation to trade. Sarathi never holds your funds or securities; every order is placed by you, on your own broker account. Derivatives trading carries substantial risk of loss.

Chart illustrations use TradingView Lightweight Charts™. Every price on this page is made up.

TradingView and Telegram are trademarks of their respective owners. Sarathi is not affiliated with or endorsed by them.