How TradingView strategy order-fill alerts reach a webhook: attaching alert_message payloads to strategy.entry()/exit()/close(), the {{strategy.*}} placeholder reference, building JSON that a bot can parse, and the delivery mechanics on TradingView's side. Written with the caveat the topic demands: webhook automation inherits every discrepancy between the broker emulator's simulated fills and real execution. Educational material only, not financial advice — wiring alerts to real money without validation is how backtest fictions become live losses, and trading always carries a real risk of loss.
You attach a message to each order via the `alert_message` parameter of `strategy.entry()`, `strategy.exit()`, or `strategy.close()`, then create one alert on the strategy using the order-fills event, put `{{strategy.order.alert_message}}` in the dialog's message box, and paste your endpoint into the Webhook URL field. Each time the broker emulator fills a simulated order, TradingView sends an HTTP POST to that URL with your message as the body. That is the entire pipeline; the rest of this article is the details that make it correct.
The division of labour matters. Your Pine code decides what each order says about itself — typically a JSON snippet naming the action, quantity, and symbol. The alert dialog decides where it goes and acts as the template: {{strategy.order.alert_message}} is replaced by whatever alert_message string the filling order carried. If you leave the dialog's default message and never reference that placeholder, your carefully built payloads are silently discarded — a surprisingly common failure.
Two prerequisites. Webhook delivery is a paid-plan TradingView feature, and the alert must be created manually in the UI — no script can create or modify alerts on its own. And note what triggers the POST: a fill in the broker emulator, not your entry condition becoming true. Stops and limits can fill intrabar; market orders placed at bar close fill at the next bar's open under default settings. Your webhook receives notifications about simulated executions, which is precisely why the final section of this guide is about validating that simulation before any real money sits behind the endpoint. This is educational material, not a recommendation to automate live trading.
Placeholders are substituted by TradingView's alert engine when the alert fires. They work in the alert-dialog message box and are also resolved inside alert_message strings, so you can embed them directly in your Pine code. The strategy-specific set:
| Placeholder | Meaning |
|---|---|
| {{strategy.order.action}} | buy or sell |
| {{strategy.order.contracts}} | Quantity of the filled order |
| {{strategy.order.price}} | Fill price the emulator used |
| {{strategy.order.id}} | The id string from strategy.entry()/exit() |
| {{strategy.order.comment}} | The order's comment (falls back to the id) |
| {{strategy.order.alert_message}} | The alert_message the order carried |
| {{strategy.market_position}} | Position after the fill: long, short, flat |
| {{strategy.prev_market_position}} | Position before the fill |
| {{strategy.position_size}} | Signed position size after the fill |
General chart placeholders work too: {{ticker}} (symbol without exchange), {{exchange}}, {{interval}}, {{close}}, {{open}}, {{high}}, {{low}}, {{volume}}, {{time}} (bar time) and {{timenow}} (fire time), plus {{syminfo.currency}}.
Two correctness notes. First, {{strategy.market_position}} and {{strategy.prev_market_position}} together let a receiving bot distinguish "open a long from flat" from "reverse from short to long" — a single buy action is ambiguous between those, and bots that ignore the distinction end up with doubled or phantom positions. Second, {{strategy.order.price}} is the emulator's fill price. Your live fill will differ by spread and slippage; treat the placeholder as a reference value for logging and sanity checks, never as an assumption about what you actually got.
Here is a complete v6 strategy that attaches machine-readable JSON to its orders. The entry logic is a deliberately plain SMA cross — a vehicle for the plumbing, not a suggestion that it has merit:
//@version=6
// Educational only — validate before trading; not financial advice.
strategy("Webhook order alerts", overlay = true)
fastLen = input.int(10, "Fast SMA", minval = 1)
slowLen = input.int(30, "Slow SMA", minval = 2)
fast = ta.sma(close, fastLen)
slow = ta.sma(close, slowLen)
plot(fast, "Fast SMA")
plot(slow, "Slow SMA")
longSig = ta.crossover(fast, slow)
flatSig = ta.crossunder(fast, slow)
// Placeholders inside alert_message are resolved when the alert fires.
entryMsg = '{"event":"entry","action":"{{strategy.order.action}}","ticker":"{{ticker}}","qty":"{{strategy.order.contracts}}","price":"{{strategy.order.price}}","pos_after":"{{strategy.market_position}}"}'
exitMsg = '{"event":"exit","action":"{{strategy.order.action}}","ticker":"{{ticker}}","qty":"{{strategy.order.contracts}}","price":"{{strategy.order.price}}","pos_after":"{{strategy.market_position}}"}'
if longSig and barstate.isconfirmed and strategy.position_size == 0
strategy.entry("Long", strategy.long, alert_message = entryMsg)
if flatSig and barstate.isconfirmed and strategy.position_size > 0
strategy.close("Long", alert_message = exitMsg)In the alert dialog, the message box contains exactly {{strategy.order.alert_message}} — nothing else — so each fill emits its own JSON. Note the single quotes around the Pine string literals: JSON needs double quotes inside, and Pine lets you avoid escaping by delimiting the literal with '.
Because alert_message accepts a series string in v6, you can also inject script-computed values with str.tostring() — an ATR reading, a computed stop level — alongside the placeholders. Keep the payload flat and explicit: receiving code should never have to infer intent from a bare buy.
On the wire, a webhook alert is an HTTP POST to your URL with the resolved message as the request body. If the message parses as JSON, TradingView sends it with an application/json content type; otherwise it goes as plain text — so a malformed payload does not error, it just arrives as text your parser rejects. Only ports 80 and 443 are supported, and your endpoint should respond quickly with a 2xx; a slow or erroring endpoint risks missed deliveries, and TradingView is not a message queue — there is no durable retry contract you should design around.
That last point deserves expansion, because it is the part most tutorials omit. Treat webhook delivery as at-most-once. Alerts can be missed if your server is down at the moment of the POST; alerts can also stop entirely because the alert on TradingView's side was deactivated — which happens automatically whenever you edit the strategy's inputs or code, since the alert is bound to the script instance that existed when it was created. Silent alert death after an innocent settings tweak is probably the single most common way live automations quietly stop trading.
Defensive practices on the receiving side, in order of value: validate that the payload is your own (a shared secret field inside the JSON, since you control the body), make handlers idempotent so a duplicate or replayed message cannot double an order, reconcile your bot's believed position against the broker's actual position rather than trusting the running sum of messages, and log every raw body before parsing. Restricting inbound traffic to TradingView's published alert IP addresses adds a further layer. None of this is optional once real orders sit behind the endpoint — which, to be clear, is not something this article encourages you to rush into.
A webhook does not upgrade a strategy; it faithfully transmits whatever the broker emulator says happened. Every known gap between TradingView's simulation and live execution flows straight through the pipe into real orders.
Start with fills. The emulator fills market orders at the next bar's open by default, assumes your stop and limit orders fill at their exact prices, and models intrabar price movement with assumptions about whether the high or low came first. Live, you get spread, slippage, and occasionally a gap straight through your stop. {{strategy.order.price}} reports the simulation, and your bot will execute somewhere else. On top of that sits latency: the alert fires, TradingView POSTs, your server processes, your broker acknowledges — seconds in which fast markets move. A strategy whose backtest edge lives in the first tick after a signal transmits an edge that cannot survive its own delivery time.
Then there is the deeper problem: the backtest itself. If the strategy repaints, overfits, or leans on lookahead-contaminated request.security() data, the webhook automates a fiction with perfect reliability. Automation removes the human hesitation that would otherwise have caught the discrepancy after the first few odd fills.
A sane validation sequence before any live capital: run the ForexCodes audit (or an equivalent deterministic review) for repainting and lookahead issues; run the alert pipeline into a logging endpoint for weeks and diff every payload against the chart; then paper-trade the full loop — alert to bot to simulated broker — and reconcile positions daily. Only after all three layers agree should real order routing even be a discussion, sized so that a total pipeline failure is survivable.
None of this is financial advice, and nothing here implies the strategy being automated is profitable. A webhook is a wire. Trading over it carries a real risk of loss.
Educational only — not financial advice. Trading involves substantial risk of loss.