Signal Formatting
How to correctly format webhook signals in CSV, JSON, and minified JSON; field order requirements; all available parameters and their constraints; common formatting errors and fixes.
Overview#
PineHook accepts webhook signals in two interchangeable formats: CSV (plain text) and JSON. The platform auto-detects the format on each request, so you can choose whichever is easier to write in TradingView's alert editor. Every signal requires three core fields: a license key, a command, and (for most commands) a symbol, plus whatever parameters the command needs. Signals that fail validation are rejected immediately, with the reason returned to TradingView and recorded in the Signal Logs dashboard.
How It Works#
- TradingView fires an alert and posts the payload to your webhook URL.
- The platform detects the format: payloads that begin with
{are parsed as JSON; all other payloads are parsed as CSV. - The three core fields (license key, command, and symbol) are extracted and validated first.
- Additional parameters are parsed and validated. If any required parameter is missing, out of range, or conflicts with another, the signal is rejected at this step.
- On rejection, the platform records the reason in Signal Logs (provided the license key was valid and extractable) and returns an HTTP 400 response to TradingView with the rejection reason in the body.
- On success, the signal is queued and delivered to the connected EA. TradingView shows a green result on the alert's execution history.
Duplicate and Repeated Alerts#
TradingView re-sends a webhook when its first attempt is slow to respond or returns a server error, and those retries carry a byte-identical payload. To stop one alert from opening a position twice, the platform collapses identical repeats: if the exact same payload for the same license arrives again within a short window (about 12 seconds), it is treated as a duplicate — the original signal's result is returned, no second copy is queued, and the trade is not executed again.
- You do not need to worry about TradingView's automatic retries double-filling an order.
- A strategy that intentionally fires the very same command twice within ~12 seconds (identical symbol, side, size, and parameters) will see the second one collapsed into the first. If you genuinely want two identical entries that fast, vary something in the payload or space them more than ~12 seconds apart.
- The window is short by design, so normal trading — even many different signals per minute — is unaffected.
Signal Formats#
CSV#
CSV uses plain text with comma-separated fields on a single line.
Structure:
LICENSE_KEY,action,SYMBOL,param1=value1,param2=value2,...
Rules:
- The first three fields are positional and order-enforced: license key, then command, then symbol. Any other arrangement causes a field-order rejection.
- Parameters after the symbol are
key=valuepairs separated by commas. Their order after the symbol is flexible. - Each field is trimmed of leading and trailing spaces before parsing.
- Commands and parameter keys are case-insensitive (
BUY,buy, andBuyare treated identically). - Symbols are automatically uppercased on receipt (
eurusdbecomesEURUSD). - The payload must be a single line. Embedded newlines and tabs are not permitted.
Multi-symbol parameters in CSV:
Commands that accept a list of symbols (eaoffexcept, eaonexcept) separate values with semicolons within the parameter value:
LICENSE_KEY,eaoffexcept=EURUSD;GBPUSD;USDJPY
Compact multi-target TP/SL in CSV:
Multi-TP and multi-SL targets support a compact shorthand as the value of tp_pips or sl_pips. Each segment follows the pattern pips:close_percentage, separated by semicolons. The last segment may omit the percentage; the platform assigns the remainder to reach 100%. A malformed compact string (a non-numeric segment, pips ≤ 0, a close percentage over 100, or a trailing remainder that works out to 0%) is rejected with an error — it is no longer silently dropped. A complete single level such as tp_pips=30:100 (one TP closing 100%) is accepted.
LICENSE_KEY,buy,EURUSD,size_lots=0.1,tp_pips=30:50;60:30;100
This sets three targets: 30 pips (close 50%), 60 pips (close 30%), 100 pips (close remaining 20%). See the multi-target TP/SL article for full details.
JSON#
JSON uses a standard object payload. Field order is irrelevant; the platform looks up fields by key name.
Structure:
{"license": "LICENSE_KEY", "action": "buy", "symbol": "EURUSD", "size_lots": 0.1}
Rules:
- Field names for recognized keys are case-insensitive and normalized to lowercase (
"license","action","symbol"); lowercase is recommended. - Parameter values follow standard JSON typing: strings, numbers, and arrays where documented.
- Symbols are automatically uppercased on receipt, same as CSV.
- Any valid JSON is accepted: formatted (with whitespace) or minified (no whitespace).
JSON-only features:
- Multi-target TP/SL can use the
tp_targetsandsl_targetskeys, which accept arrays of objects. Each object specifies one target level usingpips,price, orpctplusclose_pctorclose_lots. The compact string syntax also works in JSON by passing it as a string value fortp_pipsorsl_pips. - Multi-symbol commands (
eaoffexcept,eaonexcept) accept asymbolskey that can be a JSON array or a comma/semicolon-separated string.
Minified JSON#
Minified JSON (no whitespace between fields) is fully supported and treated identically to formatted JSON. TradingView's alert editor often produces minified JSON naturally:
{"license":"ABCD-EF01-2345-6789","action":"buy","symbol":"EURUSD","size_lots":0.1,"sl_pips":50,"tp_pips":100}
No special handling or configuration is needed for minified payloads.
Core Required Fields#
License Key#
Identifies your license. Every signal must include it.
- CSV position: First field (position 1).
- JSON key:
"license" - Required format:
XXXX-XXXX-XXXX-XXXX: four groups of four hexadecimal characters (0-9, A-F), separated by hyphens. Case-insensitive on input. - Error on invalid format:
Invalid license key format - expected XXXX-XXXX-XXXX-XXXX (hex characters only)
Critical: If the license key is missing or fails format validation, no Rejected Signals row is created. The rejection is still returned to TradingView, but you will not see it in Signal Logs. Always confirm the key is present and correctly formatted before checking for a Rejected Signals entry.
Command (Action)#
Tells the EA what action to perform. Commands are case-insensitive.
- CSV position: Second field (position 2).
- JSON key:
"action" - Error if missing:
Missing 'action' - signals require: license, action, symbol - Error if unrecognized (close match found):
Unknown command '{command}' - did you mean '{suggestion}'? - Error if unrecognized (no close match):
Unknown command '{command}' - valid commands include: buy, sell, closelong, closeshort, closeall, etc. - Deprecated name:
canceldelaywas renamed toclearqueue. Sending the old name returns an error pointing toclearqueue; it does not silently execute.
Symbol#
The trading instrument the command applies to.
- CSV position: Third field (position 3).
- JSON key:
"symbol" - Required format: 1 to 20 uppercase alphanumeric characters (A-Z, 0-9). No slashes, dashes, dots, spaces, or other special characters. Automatically uppercased on receipt.
- Error on invalid format:
Invalid symbol '{symbol}'. Symbols must be 1-20 uppercase alphanumeric characters (A-Z, 0-9). - Error if missing (when required):
Missing 'symbol' - signals require: license, action, symbol
Commands that do not require a symbol in the standard third-field position: eaon, eaoff, eaoffexcept, eaonexcept, closeall, closecancelall, cancelall, clearqueue, closealleaoff, closecancelalleaoff, closeallprofit, closeallloss
eaoffexcept and eaonexcept still require at least one symbol in the parameter value (e.g., eaoffexcept=EURUSD;GBPUSD). Sending either with no symbols is rejected and surfaces in Signal Logs → Rejected Signals with reason "missing_symbol". Use eaoff,ALL or eaon,ALL for the all-symbols cases.
Symbol filter list size limit: the symbol filter (in either blacklist or whitelist mode) is capped at 100 symbols. Any EA control command (eaon, eaoff, eaoffexcept, eaonexcept, or an inline eaon= / eaoff= parameter on a trade command) that would leave the filter with more than 100 entries is rejected with block_reason="filter_list_too_long". The rejection message reports the would-be size and suggests switching to the inverse filter mode (e.g., "you're blacklisting 100+ symbols, consider a whitelist of just the few you want to allow"). The filter is not modified when this happens.
Commands Reference#
| Command | Category | Symbol Required | Description |
|---|---|---|---|
buy | Market order | Yes | Open a long market position |
sell | Market order | Yes | Open a short market position |
buylimit | Pending order | Yes | Place a buy limit order (entry below current price) |
buystop | Pending order | Yes | Place a buy stop order (entry above current price) |
selllimit | Pending order | Yes | Place a sell limit order (entry above current price) |
sellstop | Pending order | Yes | Place a sell stop order (entry below current price) |
buyat | Pending order | Yes | Alias: buy limit or stop, direction inferred from price vs. market |
sellat | Pending order | Yes | Alias: sell limit or stop, direction inferred from price vs. market |
closelong | Close | Yes | Close open long positions on the symbol |
closeshort | Close | Yes | Close open short positions on the symbol |
closeall | Close | No | Close all open positions |
closeallprofit | Close | No | Close all net-profitable positions — P&L incl. swap+commission (optional minprofit price-pip filter) |
closeallloss | Close | No | Close all net-losing positions — P&L incl. swap+commission (optional maxloss price-pip filter) |
closecancelall | Close + cancel | No | Close all positions and cancel all pending orders |
cancellong | Cancel | Yes | Cancel pending buy orders on the symbol |
cancelshort | Cancel | Yes | Cancel pending sell orders on the symbol |
cancelall | Cancel | No | Cancel all pending orders |
clearqueue | Queue | No | Cancel all queued delayed signals for this license |
trail | Trade management | Yes | Add or update a trailing stop on matching positions |
notrail | Trade management | Yes | Remove trailing stop monitors from matching positions |
breakeven | Trade management | Yes | Add or update an auto-breakeven on matching positions |
nobreakeven | Trade management | Yes | Remove auto-breakeven monitors from matching positions |
modify | Trade management | Yes | Modify the SL or TP of existing positions or pending orders |
eaon | EA control | No | Enable trading on a symbol (pass symbol as eaon=SYMBOL) |
eaoff | EA control | No | Disable trading on a symbol (pass symbol as eaoff=SYMBOL) |
eaonexcept | EA control | No (third field), but at least one symbol required in the parameter value | Enable trading on all symbols except those listed |
eaoffexcept | EA control | No (third field), but at least one symbol required in the parameter value | Disable trading on all symbols except those listed |
closealleaoff | Emergency | No | Close all positions, then disable the EA |
closecancelalleaoff | Emergency | No | Close all positions, cancel all pending orders, disable the EA |
Parameters Reference#
Position Sizing#
Required for all trade-opening commands (buy, sell, and all pending order commands). Use exactly one.
| Parameter | Description and Constraints |
|---|---|
size_lots | Fixed position volume in lots (e.g., size_lots=0.1). Use any positive value your broker permits. |
size_pct | Risk-based sizing: percentage of account balance to risk on this trade (greater than 0 and up to 100). Requires a stop loss parameter; without an SL distance, lot size cannot be computed. |
- Using both in the same signal: rejected with
Conflicting position sizing parameters: size_lots, size_pct. Use either size_lots (fixed volume) or size_pct (risk-based), not both. - Omitting both on a trade-opening command: rejected with
Position sizing is required. Specify size_lots (fixed volume, e.g. size_lots=0.1) or size_pct (risk-based, e.g. size_pct=1.5)... size_pctwithout a stop loss: rejected withsize_pct requires a stop loss to calculate position size...; addsl_pips,sl_price, orsl_pct.
Stop Loss#
Optional for most commands, but required when size_pct is used. Use at most one.
| Parameter | Description and Constraints |
|---|---|
sl_pips | Stop loss distance in pips from the entry price |
sl_price | Stop loss as an absolute price level. Must be positive; sl_price=0 is rejected, and a value above roughly 1,000,000,000 (1e9) is rejected with HTTP 400 (matching the per-target slN_price cap). |
sl_pct | Stop loss distance as a percentage of the entry price |
Using more than one format in the same signal: Conflicting stop loss parameters: {params}. Use only one of: sl_pips, sl_price, or sl_pct.
Take Profit#
Optional. Use at most one.
| Parameter | Description and Constraints |
|---|---|
tp_pips | Take profit distance in pips from the entry price |
tp_price | Take profit as an absolute price level. Must be positive; tp_price=0 is rejected, and a value above roughly 1,000,000,000 (1e9) is rejected with HTTP 400 (matching the per-target tpN_price cap). |
tp_pct | Take profit distance as a percentage of the entry price |
Using more than one format: Conflicting take profit parameters: {params}. Use only one of: tp_pips, tp_price, or tp_pct.
Multi-Target TP/SL (Up to 10 Levels)#
Splits the position across multiple take-profit or stop-loss price targets. See the multi-target TP/SL article for full syntax documentation.
Key constraints:
- Levels must be numbered sequentially from 1 with no gaps (
tp1,tp2,tp3, etc.). Skipping fromtp1totp3without atp2is rejected. - Close percentages across all levels must sum to exactly 100%.
- Cannot mix
_close(percentage) and_lots(fixed volume) on the same side. - Maximum of 10 targets per side.
Available input modes:
- Individual parameters:
tp1_pips=30,tp1_close=50,tp2_pips=60,tp2_close=30, etc. - Compact string (CSV or JSON string value):
tp_pips=30:50;60:30;100 - JSON array (JSON only):
"tp_targets": [\{"pips": 30, "close_pct": 50\}, ...]
Pending Order Entry Price#
| Parameter | Description and Constraints |
|---|---|
price | Entry price for limit and stop orders. Required for all pending order commands (buylimit, buystop, selllimit, sellstop, buyat, sellat). Must be a positive value. |
The platform also validates that SL and TP prices are on the correct side of the entry price when absolute prices are used:
- Buy orders: TP must be above entry; SL must be below entry.
- Sell orders: TP must be below entry; SL must be above entry.
Trailing Stop#
Use exactly one distance mode. These parameters are also valid on trail commands issued separately after a position opens. See the trailing stop and breakeven article for full details.
| Parameter | Description and Constraints |
|---|---|
distance_pips | Trail distance in pips. A value above roughly 1,000,000 is rejected with HTTP 400. |
distance_pct | Trail distance as a percentage of entry price (greater than 0, up to 99) |
distance_atr | Trail distance as an ATR multiplier (0.1 to 20.0) |
trail_trigger | Pips of profit required before the trail activates. A value above roughly 1,000,000 is rejected with HTTP 400. |
trail_trigger_pct | Percentage profit required before activation (0–100). A value above 100 is rejected with HTTP 400, on both the standalone trail command and an inline trail on an opening command. |
trail_trigger_price | Exact price level that triggers trail activation |
trail_step | Minimum pips the SL must move per update (must be positive). Use with distance_pips. A value above roughly 1,000,000 is rejected with HTTP 400. |
trail_pct_step | Minimum percentage movement per update. Use with distance_pct. Automatically capped at the distance value. |
trail_atr_period | ATR calculation period in bars (2-500, default 14). Use with distance_atr. |
trail_atr_timeframe | ATR timeframe. Valid values: M1, M5, M15, M30, H1, H4, D1, W1, MN1. An unrecognized value is rejected with HTTP 400 (recorded in Signal Logs -> Rejected Signals). |
Trigger modes are mutually exclusive: use at most one of trail_trigger, trail_trigger_pct, trail_trigger_price.
The pips-based trailing fields (distance_pips, trail_step, trail_trigger) reject a finite-but-absurd value above roughly 1,000,000 pips; their _pct/_atr siblings were already bounded. An absurd value placed the trailing stop so far away it never engaged, leaving you with a silently dead trailing stop you believed was active. A legitimately wide trailing distance is unaffected.
Auto-Breakeven#
Use exactly one trigger mode and exactly one offset mode. See the trailing stop and breakeven article.
| Parameter | Description and Constraints |
|---|---|
be_trigger_pips | Pips of profit required before moving the SL to breakeven. be_trigger_pips=0 fires immediately on position open. |
be_trigger_pct | Percentage profit required. be_trigger_pct=0 fires immediately. |
be_trigger_price | Exact price level that triggers the breakeven move. be_trigger_price=0 means no trigger. |
be_offset_pips | How far above or below entry to place the SL. Positive values lock in profit; negative values allow a buffer below entry. |
be_offset_pct | Offset as a percentage of the entry price |
be_offset_price | Absolute SL price to move to |
Trigger modes are mutually exclusive; offset modes are mutually exclusive.
Trade Filtering#
These parameters narrow which positions or orders a command affects.
| Parameter | Description and Constraints |
|---|---|
magic | Magic number (integer 0 to 2,147,483,647, or ALL). N restricts to that exact strategy; 0 = magic-0 (manual trades); ALL = every magic (wildcard) on close/cancel/modify commands only, and is rejected on opening commands (buy, sell, pending orders); negatives and values above 2,147,483,647 are rejected. Omit to use your default magic. |
close_pct | Partial close percentage (greater than 0, up to 100). Cannot combine with close_lots. |
close_lots | Partial close in fixed volume (must be positive). Cannot combine with close_pct. |
minprofit | For closeallprofit: minimum favourable movement in price pips a position must have to be closed. (Winner/loser is decided on net P&L incl. swap+commission; this is a separate pip filter.) Must be 0 or greater. |
maxloss | For closeallloss: maximum adverse movement in price pips a position is allowed to have before it is closed. (Net P&L incl. swap+commission decides "loser"; this caps the pip magnitude.) Must be 0 or greater. |
direction | For modify: filter to long or short positions only. |
target | For modify: apply only to sl, tp, or both. |
order_type | For modify: target pending orders of a specific type (buystop, buylimit, sellstop, selllimit). Cannot be combined with direction. |
Spread and Slippage Protection#
| Parameter | Description and Constraints |
|---|---|
maxspread_pips | Maximum allowed spread in pips at execution time. The EA skips execution if the spread exceeds this value at the moment the order is placed. |
maxspread_pct | Maximum allowed spread as a percentage of price (0 to 100). |
marketprice | Reference market price from the TradingView alert. Used for slippage comparison at execution. Use the current price variable from TradingView's alert engine. |
Timing and Scheduling#
| Parameter | Description and Constraints |
|---|---|
delay | Seconds to wait before executing the signal (paid plans only). Maximum 86,400 seconds (24 hours). |
closetime | Seconds after the position opens to automatically close it. Range: 1 to 2,592,000 (30 days). |
expiry | Seconds until a pending order expires. Subject to a tier-based cap; exceeding the cap causes rejection. |
expiry_time | ISO 8601 datetime for pending order expiration (e.g., 2026-05-01T14:00:00Z). A value with no timezone suffix is treated as UTC. A malformed value is rejected with HTTP 400 (recorded in Signal Logs -> Rejected Signals); omit the field for no expiry. |
active_start | Per-signal active window start time in HH:MM (24-hour format). Overrides this license's active hours setting for this signal only. A malformed value (e.g. 25:00, or a missing leading zero like 9:00) is rejected with HTTP 400 (recorded in Signal Logs -> Rejected Signals). Must be paired with active_end; active_start and active_end must differ — an equal/zero-length window (incl. 00:00/00:00) is rejected with HTTP 400. Omit both for an always-on window. |
active_end | Per-signal active window end time in HH:MM (24-hour format). Same pairing and zero-length-window rules as active_start. |
Other Parameters#
| Parameter | Description and Constraints |
|---|---|
auth | Signal authentication password for this license. When Signal Authentication is enabled on the license, every signal must include it or the signal is blocked; when the feature is off, no signal needs it (a value sent while off is parsed then ignored). Maximum 72 characters. See the signal authentication article for details. |
comment | Accepted for compatibility but not applied to the order. The broker order comment is reserved for PineHook's internal signal-ID tag (SIG:<id>), so a custom comment has no effect. If sent, it still counts toward the payload size limit. |
Payload Size Limit#
All signals must be 2,048 bytes or smaller. Signals exceeding this size return HTTP 413 (payload too large). No Rejected Signals row is created for oversized payloads; the rejection appears only in the TradingView alert response. The most common cause is an unusually long comment value.
Error Messages and Meanings#
Group A: Validation Rejections (HTTP 400)#
Surface: Signal Logs → Rejected Signals tab AND TradingView alert execution history.
A Rejected Signals row is only created when the platform can extract a valid license key from the payload. If the key is absent or fails format validation, no row appears in Signal Logs. The rejection is still returned to TradingView. Very long rejection messages may be shortened.
| Message | Cause | What to do |
|---|---|---|
Invalid license key format - expected XXXX-XXXX-XXXX-XXXX (hex characters only) | License key does not match the required four-group hex format | Copy the key directly from the Licenses page in the dashboard. Do not retype it manually. |
Missing 'license' - signals require: license, action, symbol | No license key found in the payload | Add the license key as the first CSV field or as "license" in JSON |
Missing 'action' - signals require: license, action, symbol | No command found in the payload | Add the command as the second CSV field or as "action" in JSON |
Missing 'symbol' - signals require: license, action, symbol | Symbol field absent for a command that requires one | Add the symbol as the third CSV field or as "symbol" in JSON |
Wrong field order: '{value}' looks like a command but is in the symbol position. Correct order is: LICENSE,action,SYMBOL | CSV fields are in the wrong order; a command name appears in the symbol position | Reorder the CSV fields: license key first, command second, symbol third |
Unknown command '{command}' - did you mean '{suggestion}'? | Command name is misspelled; the platform found a close match | Use the suggested correction |
Unknown command '{command}' - valid commands include: buy, sell, closelong, closeshort, closeall, etc. | Command name is unrecognized with no close match | Check the Commands Reference in this article and correct the name |
canceldelay was renamed to 'clearqueue' for clarity... | Deprecated command name used | Replace canceldelay with clearqueue in the alert |
Invalid symbol '{symbol}'. Symbols must be 1-20 uppercase alphanumeric characters (A-Z, 0-9). | Symbol contains unsupported characters (slash, dash, space, etc.) | Remove special characters; use the broker's symbol name without separators (e.g., EURUSD not EUR/USD) |
Position sizing is required. Specify size_lots (fixed volume, e.g. size_lots=0.1) or size_pct (risk-based, e.g. size_pct=1.5)... | Trade-opening command has no size parameter | Add size_lots=0.1 (or a suitable volume) or size_pct=1.5 |
Conflicting position sizing parameters: size_lots, size_pct. Use either size_lots (fixed volume) or size_pct (risk-based), not both. | Both size_lots and size_pct appear in the same signal | Remove one; use only size_lots or only size_pct |
Invalid size_pct={value}. Must be > 0 and <= 100 (percentage of account balance to risk on this trade). | size_pct is 0, negative, or above 100 | Use a value between 0 (exclusive) and 100 (inclusive) |
Invalid size_lots={value}. Must be greater than 0 (fixed trade volume). | size_lots is 0 or negative on a trade-opening command | Use a positive lot size; a negative size_lots would otherwise silently fall back to the EA's default lot |
size_pct requires a stop loss to calculate position size... | size_pct present but no stop loss parameter included | Add sl_pips, sl_price, or sl_pct; the EA requires the SL distance to compute lot size |
Conflicting stop loss parameters: {params}. Use only one of: sl_pips, sl_price, or sl_pct. | More than one SL format type in the same signal | Remove all but one SL parameter |
Conflicting take profit parameters: {params}. Use only one of: tp_pips, tp_price, or tp_pct. | More than one TP format type in the same signal | Remove all but one TP parameter |
sl_price=0 is not valid. Use a positive price value to set the stop loss. | sl_price set to 0 | Use a positive price level, or omit sl_price to open without a stop loss |
tp_price=0 is not valid. Use a positive price value to set the take profit. | tp_price set to 0 | Use a positive price level, or omit tp_price to open without a take profit |
Invalid sl_pips={value}. Must be a positive distance — a negative value would open the position with no stop/target... | A base stop/target distance or percentage (sl_pips, tp_pips, sl_pct, tp_pct) is negative on a trade-opening command | Use a positive value. A negative stop would otherwise be silently treated as "no stop" and open the position unprotected — the signal is rejected instead |
Invalid sl_price={value}. A stop-loss price must be positive. (and tp_price) | A negative absolute SL/TP price on a trade-opening command | Use a positive price level; a negative price would silently drop the stop and open the position unprotected |
{COMMAND} price={value} is not valid. The entry price must be a positive value... | A pending order sent with price=0 or a negative price | Provide a positive entry price (e.g. price=1.0800) |
{COMMAND} requires a price parameter specifying the entry level. Example: price=1.0800 | Pending order command sent without a price parameter | Add price= with the desired entry price |
Take profit (tp_price={value}) must be above entry price ({entry}) for BUYLIMIT orders... | TP price is below the entry price on a buy order | Move TP above the entry price; for buy orders, TP must be higher than entry |
Stop loss (sl_price={value}) must be below entry price ({entry}) for BUYLIMIT orders... | SL price is above the entry price on a buy order | Move SL below the entry price; for buy orders, SL must be lower than entry |
Take profit (tp_price={value}) must be below entry price ({entry}) for SELLLIMIT orders... | TP price is above the entry price on a sell order | Move TP below the entry price; for sell orders, TP must be lower than entry |
Stop loss (sl_price={value}) must be above entry price ({entry}) for SELLLIMIT orders... | SL price is below the entry price on a sell order | Move SL above the entry price; for sell orders, SL must be higher than entry |
Gap detected in TP target numbering. Missing: TP{n}. Found: TP1, TP3. Targets must be numbered sequentially starting from TP1 with no gaps. | TP levels skip a number | Add the missing level, or renumber all levels starting from 1 with no gaps |
Too many TP/SL targets in compact syntax: {N} provided, maximum is 10. | Compact TP/SL string has more than 10 segments | Reduce to 10 targets or fewer |
Multi-TP close percentages must sum to 100% (got {total}%) | Close percentages across all TP levels do not total 100 | Adjust the values until they sum exactly to 100 |
Conflicting TP{n} level parameters: tp{n}_pips, tp{n}_price. Use only one of: tp{n}_pips, tp{n}_price, or tp{n}_pct. | A single TP level uses more than one price format | Keep only one format for that level |
trail command requires a distance parameter. Specify one of: distance_pips, distance_pct, or distance_atr. | trail command sent without any distance parameter | Add distance_pips, distance_pct, or distance_atr |
Conflicting trailing stop parameters: {params}. Use only one of: distance_pips, distance_pct, or distance_atr. | Multiple trail distance types in the same signal | Keep only one distance type |
trail_step={value} is invalid. Must be greater than 0 (minimum SL move in pips). | trail_step is 0 or negative | Use a positive value |
Conflicting trail trigger parameters: {params}. Use only one of: trail_trigger, trail_trigger_pct, or trail_trigger_price. | Multiple trail trigger types in the same signal | Keep only one trigger type |
Conflicting breakeven trigger parameters: {params}. Use only one of: be_trigger_pips, be_trigger_pct, or be_trigger_price. | Multiple breakeven trigger types in the same signal | Keep only one trigger type |
Conflicting breakeven offset parameters: {params}. Use only one of: be_offset_pips, be_offset_pct, or be_offset_price. | Multiple breakeven offset types in the same signal | Keep only one offset type |
Conflicting expiry parameters: expiry (seconds) and expiry_time (datetime). Use only one. | Both expiry and expiry_time are present on the same pending order | Keep only one — the EA would otherwise silently honor expiry and discard expiry_time |
`delay={value}` is not valid. Use a positive number of seconds... | delay is negative | Use a positive number of seconds, or omit delay for immediate dispatch |
Unknown parameter '{param}' for {command} command... Check the spelling, see https://pinehook.io/docs/commands for the full list. | Unrecognized parameter name on a trade management command | Fix the spelling; trade management commands reject unknown parameters. Even one typo causes rejection. |
Unrecognized parameter - did you mean '{suggestion}'? | Unknown parameter on a market or close command; a close match was found | Fix the parameter name using the suggestion |
Invalid value '{value}' - expected a number (non-ASCII characters not allowed) | A numeric field contains non-ASCII digits (e.g., Unicode fullwidth numerals) | Use standard ASCII numerals only |
Invalid value '{value}' - must be a finite number | A numeric field contains NaN or Infinity | Replace with a valid real number |
Group B: Plan and Rate Limit Rejections#
Surface: TradingView alert response: HTTP 402 for the plan gates (delay requires a paid plan, expiry cap exceeded); HTTP 429 for the queue-full and rate-limit rejections. Rate-limited signals (HTTP 429) are recorded: they appear in Signal Logs as a Blocked row and in Rejected Signals, with reason rate_limited (per-minute) or rate_limited_daily (per-day).
| Message | Cause | What to do |
|---|---|---|
Delayed signals require a paid plan. Upgrade to use delay=. | delay parameter used on a Trial account | Upgrade the subscription or remove the delay parameter from the alert |
Pending-order expiry exceeds this plan's {minutes}-minute cap. Upgrade for longer expiries. | expiry value exceeds what the current plan allows | Reduce the expiry duration or upgrade to a plan with a higher cap |
Delayed-signal queue is full for this license. Cancel pending signals with clearqueue, remove them individually from Signal Logs on your PineHook dashboard, or wait for them to execute. | Too many delayed signals are already queued for this license (returns HTTP 429; this one does appear in Rejected Signals) | Send a clearqueue signal to cancel pending delayed signals, then retry |
Group C: Payload Size (HTTP 413)#
Surface: TradingView alert response only. No Rejected Signals row is created.
| Message | Cause | What to do |
|---|---|---|
Request entity too large | Signal payload exceeds 2,048 bytes | Shorten the comment field or remove unusually long string values |
Group D: Signal Authentication (HTTP 200, status blocked)#
Surface: Signal Logs → Rejected Signals tab AND the TradingView alert response.
Signal-authentication failures are not validation rejections. The request is well-formed and the license key is valid, so the platform returns HTTP 200 with a body of {"status": "blocked", "reason": ..., "message": ...} rather than a 4xx error. The signal is recorded as blocked and surfaces in the Rejected Signals tab. (An unknown or invalid license key is different: that is an HTTP 401, not a signal-auth block.)
| Message | Reason code | Cause | What to do |
|---|---|---|---|
Authentication required | missing_auth | Signal Authentication is enabled on this license and the auth parameter is missing from the signal (every signal for the license must carry it while the feature is on) | Add auth=PASSWORD to the signal; see the signal authentication article |
Invalid authentication | invalid_auth | Signal Authentication is enabled and the password is incorrect or exceeds 72 bytes | Verify the password in the dashboard matches what the alert sends; check for case sensitivity and character count |
Where to look#
| Situation | Where to look |
|---|---|
| You see a rejection and want to understand the reason | Signal Logs → Rejected Signals tab in the dashboard |
| Your signal was rejected but nothing appears in Rejected Signals | The license key in the payload is missing or malformed. Fix the key format first; once valid, rejections will appear in Signal Logs. |
| HTTP 429 (rate limit exceeded) | Dashboard → Subscription page; signal frequency limits are plan-specific |
| HTTP 413 (payload too large) | The alert payload exceeds 2,048 bytes. Shorten the comment or other long string fields. |
| "Authentication required" or "Invalid authentication" | Signal Logs → Rejected Signals tab AND the signal authentication article |
| Your signal is not in Rejected Signals and not rate-limited, but TradingView shows a failure | Confirm the webhook URL is correct and the request is reaching the platform; check if the EA is connected |
Plan and Tier Differences#
| Feature or Behavior | Trial | Standard | Pro | Premium |
|---|---|---|---|---|
| CSV and JSON signal delivery | Available | Available | Available | Available |
Multi-target TP/SL (tp1-tp10, sl1-sl10) | Available | Available | Available | Available |
Trailing stop (trail) and auto-breakeven (breakeven) commands | Available | Available | Available | Available |
delay parameter (queued signal execution) | Not available | Available | Available | Available |
Pending order expiry cap (expiry, expiry_time) | 15-min cap | 24-hour cap | 24-hour cap | 24-hour cap |
| Signal frequency limit | Plan limit applies | Plan limit applies | Higher limit | Highest limit |
When a Trial period ends with no paid plan active, trade-opening signals are received but not executed. Close and cancel commands remain operational regardless of plan state.
Known Edge Cases and Gotchas#
-
CSV third-field ambiguity: If the third comma-separated value contains an
=character, the parser treats it as akey=valueparameter rather than the symbol. Always place the symbol as a plain value with no=sign, and put all parameters after it. -
Automatic symbol uppercasing: Symbols are silently uppercased on receipt.
eurusdandEURUSDproduce identical results. This is by design but can confuse you if you compare your alert text against the uppercase value you see in Signal Logs. -
canceldelayis deprecated and does not execute: Sending this old command name returns an error pointing toclearqueue. It does not silently run the equivalent action. -
size_pctrequires a stop loss: Withoutsl_pips,sl_price, orsl_pctin the same signal, the signal is rejected. The EA needs the SL distance to compute position size from a risk percentage. -
sl_price=0andtp_price=0are invalid: Zero is not accepted as a price value. Omit the field to open a position without a stop loss or take profit. -
sl_pips/tp_pipshave an upper bound: A pip distance above roughly 1,000,000 is rejected with HTTP 400 (sl_pips is implausibly large …), mirroring thesize_lotsceiling. This catches an absurd typo such assl_pips=100000when you meant100— on a SELL that places the stop so far away it never triggers, leaving an effectively naked position you believe is protected. A legitimately wide stop (well under 1,000,000 pips) is unaffected. -
Compact TP/SL must be valid or the signal is rejected (HTTP 400): The close percentages must sum to exactly 100% (an optional trailing bare-
pipssegment supplies the remainder). A malformed compact string — non-numeric segment,pips≤ 0, a close percentage above 100, or a trailing remainder that resolves to 0% because earlier segments already total 100 (e.g.30:50;60:50;100) — is now rejected with a clear error, not silently discarded. Previously such a string opened a position with no targets and no error message; that footgun is closed. A complete single level liketp_pips=30:100is accepted. -
Multi-TP numbering must be sequential with no gaps: Specifying TP1 and TP3 without TP2 is rejected. Always number from 1 upward with no skipped levels.
-
A single TP/SL cannot coexist with a multi-TP/SL ladder: Sending both a single
tp_pips/tp_price/tp_pctand a multi-TP ladder (tp1_*…tp10_*) in one signal is rejected; use one or the other (the same applies to the SL side). -
Target indices above 10 are rejected:
tp11_*,sl12_*, and higher fail with an error rather than being silently ignored. The maximum is 10 levels per side. -
Per-target close volumes and distances must be positive, finite, and sane: A
tpN_lots/slN_lots(or JSONclose_lots) that is negative, zero, NaN, Infinity, or implausibly large (a close volume far above any real position, or an absurdpips/pricedistance) is rejected at the server before it reaches the EA. In the JSONtp_targets/sl_targetsarray, a level-distancepctof 0 is also rejected, and a malformed element (a non-object, or a non-numeric value) rejects the whole signal — it no longer silently drops just that level. -
modifyvalidates per-level format conflicts too: Setting two formats on the same level (e.g.modify,…,tp1_pips=50,tp1_price=…) is rejected onmodify, the same as on entry. -
modifyre-validates new levels against the broker freeze zone: Amodifythat moves a multi-TP/SL level inside the broker's minimum-stop distance (or already past current market) is skipped with a warning instead of being applied — otherwise the EA's tick-by-tick manager would fire it as an instant, unintended partial close. Move the level further from market and resend. -
Unrecognized parameters cause rejection even on market-order commands: A typo such as
sl_pipinstead ofsl_pipscauses the signal to fail with HTTP 400. Trade management commands (trail,breakeven,modify, etc.) are even stricter: they reject any parameter name they do not recognize, including parameters that are valid on other command types. -
Non-ASCII digits in numeric fields are rejected: Unicode fullwidth numerals (used in some Asian character sets) and other non-ASCII digit substitutes cause an explicit error. Use standard ASCII digits only.
-
Multi-symbol syntax differs between CSV and JSON: In CSV, list symbols semicolon-separated inside the parameter value (e.g.,
eaoffexcept=EURUSD;GBPUSD). In JSON, use thesymbolskey with an array or comma/semicolon-separated string. Theeaonandeaoffparameters in JSON accept only one symbol; useeaonexceptoreaoffexceptfor multiple symbols. -
Payload limit is enforced at the gateway: Signals over 2,048 bytes return HTTP 413 and do not create a Rejected Signals row. The
commentfield is the most frequent cause of oversized payloads. -
trail_atr_timeframeis rejected on unrecognized values: A misspelled or unsupported timeframe value (e.g.,1Hinstead ofH1) is rejected with HTTP 400 and recorded in Signal Logs -> Rejected Signals, rather than silently falling back to a default. Valid values are: M1, M5, M15, M30, H1, H4, D1, W1, MN1. -
authis position-independent in CSV: Theauthparameter can appear anywhere in the parameter list (before or after other parameters) and is always extracted by key name, not by position. -
Rate-limited signals are recorded: An HTTP 429 (per-minute or per-day cap) writes a Blocked row in Signal Logs and a row in Rejected Signals, with reason
rate_limitedorrate_limited_daily. If you're missing signals during a burst, look for these Blocked/Rejected rows to confirm you're hitting your plan's frequency limit. (Close and cancel commands are exempt from the caps.)
Frequently Asked Questions#
Q: Do I have to use JSON, or can I use CSV? A: Either format works for any command. Both CSV and JSON are fully supported everywhere. Choose whichever is easier to write in TradingView's alert message editor.
Q: Does field order matter in JSON? A: No. JSON field order is irrelevant; the platform looks up fields by key name. Field order only matters in CSV, where the first three values must appear as license key, then command, then symbol.
Q: My symbol includes a slash (like EUR/USD). Why is it rejected?
A: Symbols must contain only letters (A-Z) and digits (0-9), with no separators. Use EURUSD instead of EUR/USD. Check the exact symbol name in the MT5 Market Watch window; brokers sometimes use suffixes such as EURUSDm or EURUSD.r.
Q: Can I include both size_lots and size_pct in the same signal?
A: No. They are mutually exclusive. Use one or the other. A signal containing both is rejected.
Q: I sent a signal and nothing appeared in Rejected Signals. What happened? A: Rejected Signals rows are only created when the platform can extract a valid, correctly formatted license key from the payload. If the key is missing or malformed, no row appears. Rate-limited rejections (HTTP 429) also do not create rows. Check the TradingView alert response for the HTTP status code and body to determine what happened.
Q: What is the difference between tp_pips and tp1_pips?
A: tp_pips sets a single take profit target for the whole position. tp1_pips is the first level of a multi-target take profit, where the position is split across multiple price levels. Use tp1_pips, tp2_pips, etc. only when splitting the position; for a single target, use tp_pips.
Q: Why is my delay parameter rejected with "requires a paid plan"?
A: The delay parameter is not available on Trial accounts. Upgrade to a Standard, Pro, or Premium plan to queue signals for delayed execution, or remove the delay parameter from the alert.
Q: Can I use minified JSON in TradingView's alert body? A: Yes, fully supported. Minified JSON and formatted JSON are handled identically. TradingView's alert editor often produces minified payloads naturally.
Q: What does the rejection message "did you mean '{suggestion}'?" mean? A: The platform detected that a parameter name in the signal closely resembles a known parameter but is not an exact match, usually a typo. Replace the parameter name with the suggestion provided in the rejection message.
Q: My pending order signal is rejected even though I included all the fields I think are required. What am I missing?
A: Three common causes: (1) the price parameter is missing; every pending order command requires an entry price; (2) a TP or SL price is on the wrong side of the entry (TP must be above entry for buy orders, below for sell orders; SL is the reverse); (3) sl_price or tp_price is set to 0, which is not a valid price. Check each against the rejection reason shown in the Rejected Signals tab.