Skip to main content

Pine Script Webhook Alerts

TL;DR

TradingView alerts can POST to any webhook URL. The alert's message field becomes the POST body. For CrossTrade, write a Pine Script that formats the message as a semicolon-separated key=value payload, then paste your CrossTrade webhook URL into the alert's Notifications tab. TradingView does the heavy lifting — your script just needs to fire the right alert() or strategy.entry() calls.

The three moving parts

  1. Pine Script — your indicator or strategy with the logic and alert() calls
  2. TradingView alert — the rule that fires and delivers messages to webhooks
  3. Webhook receiver — CrossTrade's endpoint, which parses your payload into a NinjaTrader order

Step 1: Write the Pine Script

The simplest version — an indicator() that fires a fixed-side buy alert:

//@version=6
indicator("RSI Oversold Alert", overlay=false)

rsiLen = input.int(14, "RSI length")
rsiVal = ta.rsi(close, rsiLen)

plot(rsiVal, color=color.orange, linewidth=2)
hline(30, "Oversold", color=color.green)

if ta.crossover(rsiVal, 30)
alert("BUY_SIGNAL", alert.freq_once_per_bar_close)

This fires a plain-text "BUY_SIGNAL" message when RSI crosses 30 upward. The message is static — no dynamic data.

Step 2: Create the TradingView alert

  1. On the chart with the indicator applied, click Alerts (alarm clock icon) → Create Alert
  2. Condition: select your indicator → "Any alert() function call"
  3. Options: "Once per bar close" (recommended) or "Once per bar" depending on the script
  4. Message: leave TradingView's default {{alert_message}} placeholder — this inserts whatever the script passed to alert()
  5. Notifications tab: check Webhook URL and paste your CrossTrade endpoint

Click Create. The alert is armed.

Step 3: Format the payload for CrossTrade

The message TradingView delivers is whatever string your script passes to alert(). For CrossTrade, that string needs to be a semicolon-separated key=value payload:

Modify the script:

//@version=6
indicator("RSI Oversold — CrossTrade Alert", overlay=false)

rsiLen = input.int(14, "RSI length")
rsiVal = ta.rsi(close, rsiLen)

plot(rsiVal, color=color.orange, linewidth=2)
hline(30, "Oversold", color=color.green)

// CrossTrade payload
secretKey = input.string("YOUR-SECRET-KEY", "CrossTrade secret key")
account = input.string("Sim101", "Account name")
instr = input.string("ES 06-26", "NinjaTrader instrument")
qty = input.int(1, "Quantity")

payload = "key=" + secretKey +
";command=place" +
";account=" + account +
";instrument=" + instr +
";action=buy" +
";qty=" + str.tostring(qty) +
";order_type=market" +
";tif=day"

if ta.crossover(rsiVal, 30)
alert(payload, alert.freq_once_per_bar_close)

Now the alert message is the CrossTrade-formatted payload. When fired, CrossTrade receives it, parses it, and sends a buy order to NinjaTrader.

Dynamic payloads with strategy() scripts

For entry/exit logic that goes both directions, convert to strategy() and use TradingView's built-in placeholders:

//@version=6
strategy("RSI Strategy", overlay=true, initial_capital=50000, default_qty_type=strategy.fixed, default_qty_value=1)

rsiLen = input.int(14, "RSI length")
rsiVal = ta.rsi(close, rsiLen)

if ta.crossover(rsiVal, 30)
strategy.entry("Long", strategy.long)
if ta.crossunder(rsiVal, 70)
strategy.close("Long")

Create the alert with "Order fills only" as the condition, and the following message (which uses TradingView's strategy placeholders that substitute automatically on every order fill):

key=YOUR-SECRET-KEY;
command=place;
account=Sim101;
instrument=ES 06-26;
action={{strategy.order.action}};
qty={{strategy.order.contracts}};
order_type=market;
tif=day;
sync_strategy=true;
market_position={{strategy.market_position}};
prev_market_position={{strategy.prev_market_position}};
out_of_sync=flatten;

Each time the strategy executes an entry or exit, TradingView fires the alert with these placeholders resolved. CrossTrade receives a fully-formed payload and routes it.

Alert frequency — alert.freq_*

Pine's alert() function takes a frequency:

  • alert.freq_once_per_bar_close — fires once when the bar closes. Recommended for most strategies.
  • alert.freq_once_per_bar — fires on the first tick that triggers the condition within a bar. Can fire on bars that later reverse and close differently.
  • alert.freq_all — fires on every tick the condition is true. Usually too noisy.

The safest default is alert.freq_once_per_bar_close. It means signals only fire on confirmed, closed bars — no intra-bar head-fakes.

Pitfalls that cost you trades

1. Using {{strategy.order.action}} in an indicator() script. These placeholders only work in strategy() scripts. Convert or hardcode the action. See strategy vs indicator.

2. Alert expiration. Free TradingView accounts have short alert expirations (days). Paid accounts are longer. Renew expiring alerts before they lapse.

3. Webhook rate limits. TradingView allows a limited number of alerts per plan. A strategy that fires 100 times per day will chew through alerts. Use alert.freq_once_per_bar_close and design strategies to fire sparingly.

4. Not enabling 2FA. TradingView requires 2FA on your account to enable webhook delivery. Without it, alerts fire but webhooks silently fail.

5. Silent payload errors. If your payload is malformed, CrossTrade logs the rejection but your alert appears to have fired. Always check the CrossTrade Alert History after deploying a new script.

Testing before going live

Never point a new strategy at a live account. The workflow:

  1. Sim test. Point the payload at your NinjaTrader sim account (account=Sim101). Run for multiple sessions.
  2. Paper test. Watch alert history in CrossTrade for accurate signal delivery and fill behavior.
  3. Micro live. Deploy to live with 1 MES / 1 MNQ / 1 MCL. Observe real fills, slippage, edge decay.
  4. Scale. Gradually increase size as confidence builds.

Skipping steps 1–2 is how small Pine Script bugs turn into large real-money losses.

Frequently Asked Questions

How do I send a TradingView alert to a webhook?

In the alert creation dialog, open the Notifications tab and check 'Webhook URL.' Paste your webhook endpoint. TradingView will POST the alert's message content to that URL when the alert fires. For CrossTrade, the message needs to be a semicolon-separated key=value payload.

Why isn't my Pine Script alert firing?

Common causes: (1) the alert has expired (free plan alerts expire quickly); (2) 2FA is not enabled on your TradingView account (required for webhooks); (3) the condition hasn't triggered yet — verify by temporarily using alert.freq_all to see if it fires at all; (4) the script is using a placeholder that doesn't apply (e.g., {{strategy.*}} in an indicator).

Can one alert trigger multiple webhook URLs?

No — each TradingView alert has exactly one webhook URL. To send to multiple endpoints, create duplicate alerts, each with the relevant URL. Alternatively, send to a proxy service that fans out the webhook to multiple destinations.

Do I need a paid TradingView plan for webhooks?

Yes. Webhook alerts require at least the Essential plan. Free plans do not allow webhook delivery. Paid plans also offer more alerts, longer alert durations, and faster alert execution.