Pine has two functions with "alert" in the name and two kinds of script that can call them. Which pair you pick decides what your webhook receives: a fixed sentence, a runtime string, or a message that only exists when the broker emulator fills an order. This page lays out the four combinations and what a bridge on the other end can and cannot get from each.

## The two functions

`alertcondition()` is the older one. It works in indicators only, sits in the global scope, and registers a named condition that appears as an item in the Create Alert dialog. Its `message` argument must be a constant string. The only way to get live values into it is TradingView's own placeholders, such as `{{close}}` or `{{ticker}}`, which are substituted after the fact ([TradingView help: How to use a variable value in alert](https://www.tradingview.com/support/solutions/43000531021-how-to-use-a-variable-value-in-alert/)).

`alert()` is the newer one and, in the docs' words, more or less supersedes it. It works in indicators and strategies, can be called from inside an `if` so it only runs when your condition holds, takes a `series string` message built at runtime, and lets the script choose its own frequency ([Pine Script docs: Alerts](https://www.tradingview.com/pine-script-docs/concepts/alerts/)).

```pine
alert(message, freq)
```

The `freq` argument is one of:

| Frequency | Fires |
| --- | --- |
| `alert.freq_once_per_bar` (default) | First call that executes on each live bar, possibly mid bar |
| `alert.freq_once_per_bar_close` | Only on the closing update of the live bar |
| `alert.freq_all` | Every call, on every price update |

Neither function creates a running alert on its own. Both create alert events, and a person still has to open the Create Alert dialog, pick the script, and turn the events into an alert. That alert is a snapshot of the script and its inputs; edit the script and the alert keeps running the old copy until you recreate it.

## The two kinds of script

An indicator runs on every price update of the live bar. A strategy, unless `calc_on_every_tick = true`, runs once per bar on the closing tick. That difference reshapes how the alert functions behave:

- In an indicator, `alert()` with the default frequency fires on the first tick that satisfies the condition, before the bar is confirmed.
- In a strategy without `calc_on_every_tick`, every `alert()` call effectively uses `alert.freq_once_per_bar_close` regardless of the argument, because the script only runs at bar close ([Pine Script docs: Alerts, In strategies](https://www.tradingview.com/pine-script-docs/concepts/alerts/)).
- Only a strategy has order fill events. When the broker emulator fills a simulated order, TradingView can fire the alert and substitute `{{strategy.order.alert_message}}` with whatever the order call passed as `alert_message`.

## The four combinations

|  | Indicator | Strategy |
| --- | --- | --- |
| **`alertcondition()`** | Works. Constant message plus TradingView placeholders. One dialog entry per condition. | Not available. Compile error. |
| **`alert()`** | Works. Runtime message. Fires on first matching tick unless `freq_once_per_bar_close`. | Works. Runtime message. Always at bar close unless `calc_on_every_tick`. |
| **Order fill with `alert_message`** | Not available. Indicators place no orders. | Works. Message set per order call, delivered when the emulator fills. |
| **Order fill without `alert_message`** | Not available. | Fires, but the body is the literal text `{{strategy.order.alert_message}}`. |

### Indicator with `alertcondition()`

Use it when the payload is fixed apart from price and time. The body below is valid JSON after substitution because TradingView's numeric placeholders expand without thousands separators.

```pine
//@version=6
indicator("RSI cross", overlay = false)
r = ta.rsi(close, 14)
alertcondition(ta.crossover(r, 50) and barstate.isconfirmed, "RSI up",
     '{"sym":"{{ticker}}","side":"BUY","type":"RSI","price":{{close}},"time":"{{timenow}}"}')
```

What it cannot do: include a stop computed in Pine, switch the side inside one condition, or choose the frequency. Frequency is set in the dialog, and the safe choice there is Once Per Bar Close.

### Indicator with `alert()`

Use it when the payload needs runtime values. Build the string with `str.format()` and the `{0,number,#.##}` pattern so numbers arrive ungrouped, keep TradingView placeholders outside the format string, and gate on `barstate.isconfirmed`. The [webhook alerts page](/learn/tradingview-webhook-alerts) explains the thousands-separator trap in full.

```pine
//@version=6
indicator("ORB signal", overlay = true)
orHigh = ta.highest(high, 3)[1]
orLow  = ta.lowest(low, 3)[1]
fired  = ta.crossover(close, orHigh) and barstate.isconfirmed
body   = '{"sym":"{{ticker}}","side":"BUY","type":"ORB","price":{{close}},' +
         str.format('"sl":{0,number,#.##}', orLow) + ',"time":"{{timenow}}"}'
if fired
    alert(body, alert.freq_once_per_bar_close)
```

In the dialog, choose the condition "Any alert() function call". The frequency you passed in code wins; the dialog's own frequency setting does not apply to `alert()` events.

### Strategy with `alert_message` on orders

Use it when you want the alert to mean "the emulator filled this order", with the message you attached to that order. Every order function accepts `alert_message`: `strategy.entry()`, `strategy.order()`, `strategy.exit()`, `strategy.close()` and `strategy.close_all()`. In the dialog, choose "Order fills only" or "Order fills and alert() function calls", and put `{{strategy.order.alert_message}}` in the message box, or the custom text never reaches the webhook.

```pine
if fired
    strategy.entry("L", strategy.long, alert_message = body)
strategy.exit("X", "L", stop = orLow, limit = target, alert_message = exitBody)
```

The catch is fills the script did not see. Stops and targets fill inside a bar in the emulator, and the exit's message is whatever `alert_message` held when the exit call last ran. If a `strategy.close()` or a `strategy.exit()` has no `alert_message`, the fill still fires and the body is the unexpanded placeholder.

### Strategy with `alert()`

Use it alongside `alert_message`, not instead of it. `alert()` in a strategy fires when the script decides, at bar close, and does not wait for a fill. That makes it the right carrier for a signal a human is going to confirm, because the human is the fill. Passing the same string to both keeps the two paths in step:

```pine
if fired
    strategy.entry("L", strategy.long, alert_message = body)
    alert(body, alert.freq_once_per_bar_close)
```

In the dialog, "alert() function calls only" gives you one message per decision. Adding order fills gives you a second message per fill, which a bridge has to dedupe.

## What a bridge can receive from each

| Source | Body | Timing | Dynamic values | Risk to watch |
| --- | --- | --- | --- | --- |
| Indicator `alertcondition()` | Constant plus placeholders | Per dialog frequency | Only `{{close}}` style placeholders | Wrong frequency in the dialog fires mid bar |
| Indicator `alert()` | Runtime string | Per `freq` in code | Anything in Pine | Default frequency is once per bar, not bar close |
| Strategy order fill | `alert_message` of the filled order | When the emulator fills | Anything in Pine at order time | Exits without `alert_message` send the raw placeholder |
| Strategy `alert()` | Runtime string | Bar close | Anything in Pine | Duplicate of the fill message if both are enabled |

A bridge that treats every POST as a fresh order will double count a strategy alert configured for both fills and `alert()` calls. Include a stable key in the body, either an `id` or the combination of symbol, side, strategy name and bar time, so the receiver can collapse the pair. Also decide what an unexpanded `{{strategy.order.alert_message}}` means. It is not malformed, it is an exit the script did not annotate, and the safe behaviour is to log it and do nothing.


  Frequency is set in two places. For `alertcondition()` and order fills it is
  the dialog's Once Per Bar Close setting. For `alert()` it is the `freq`
  argument in code, and the dialog setting is ignored. A strategy with
  `calc_on_every_tick = true` overrides both by running mid bar.


## Choosing

- Fixed message, indicator, no computed levels: `alertcondition()`, dialog set to Once Per Bar Close.
- Computed levels, indicator: `alert()` with `alert.freq_once_per_bar_close` and `barstate.isconfirmed`.
- Strategy, you want the backtest and the live alerts to be the same events: `alert()` at bar close, `alert_message` on every order call with the same string, `calc_on_every_tick = false`.
- Strategy, you want fills rather than decisions: order fills with `alert_message` on every call, including exits, plus a dedupe key.

The [webhook alerts page](/learn/tradingview-webhook-alerts) covers what happens to the body after it leaves TradingView, and the [repainting page](/learn/pine-repainting-lookahead-calc-on-every-tick) explains why the bar close gate matters.

## Try it in Sarathi

Sarathi's parser accepts all four paths as long as the body carries a symbol and a side, dedupes on `id` or on symbol, side, strategy and time, and treats an unexpanded `{{strategy.order.alert_message}}` as ignorable rather than as an error. (live) The Studio writes Pine v6 strategies that pass one payload string to both `alert_message` and `alert()` at bar close. Each 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. The paper broker fills confirmed orders today; Dhan and Zerodha adapters are planned. (planned) [Open the Studio](/dashboard/studio).
