Connect by Trado is a webhook-powered trading automation platform that connects your TradingView alerts (or any webhook source) directly to your broker accounts. Execute trades automatically, manage multiple accounts, and scale your strategies — all without writing a single line of code.
Instant Execution
Sub-50ms webhook processing for real-time trade execution.
Enterprise Security
AES-256-GCM encryption for all API credentials.
9 Action Types
ENTRY, EXIT, PARTIAL_EXIT, FLIP, and more.
Copy Trading
Follow expert traders or share your strategies.
Supported Brokers
Currently, Connect by Trado supports CoinDCX (Futures & Spot trading). More brokers will be added in the future.
Follow these steps to start automating your trades:
1
Create an Account
Sign up with your email or Google account. Verify your email with a 6-digit OTP.
2
Add Your Broker Account
Open "Add broker" and enter your CoinDCX API credentials (API Key + API Secret). Your credentials are encrypted with AES-256-GCM.
3
Create a Webhook
Go to "Manage Webhooks" and create a new webhook. Select your broker and give it a descriptive name.
4
Deploy Your Account
Deploy your broker account to the webhook. Set a multiplier to scale your position sizes.
5
Configure Your Alert Source
Copy the webhook URL and paste it into TradingView (or any alert source). Set up the JSON payload.
6
Start Trading
Your alerts will now automatically execute trades on your deployed broker accounts!
Broker API accounts are the bridge between Connect by Trado and your exchange. You need to add at least one API account before you can deploy to a webhook.
Navigate to Dashboard → Add broker and enter your CoinDCX account credentials.
| Field | Required | Description |
|---|---|---|
Broker | Required | Select from supported brokers (CoinDCX) |
Account Name | Required | A display name for your account (1–100 chars) |
API Key | Required | Your broker API key (encrypted at rest with AES-256-GCM) |
API Secret | Required | Your broker API secret (encrypted at rest with AES-256-GCM) |
• Credential Verification — Your API credentials are verified with the broker on creation. You can re-verify at any time.
• Balance Check — View real-time balances (available, locked, total) directly from your dashboard.
• Broker Info — Fetch your broker user profile and account details.
• Active/Inactive Toggle — Deactivated accounts skip all webhook orders. Useful for temporarily pausing execution.
• Visual ID — Each account gets a unique human-readable identifier (e.g., "A1B2C3").
Webhooks are the core of the platform. Each webhook has a unique URL that accepts JSON payloads to execute trades on all deployed accounts.
Navigate to Dashboard → Manage Webhooks and click "Create Webhook". Select a broker and provide a name and optional description.
Each webhook gets a permanent URL that never changes:
POST https://connect.trado.trade/webhook/{webhook_url_id}💡 Important: webhook_url_id vs access_code
The webhook_url_id (32 chars) is the permanent URL identifier — it never changes. The access_code (8 chars) is for sharing with subscribers and can be regenerated without affecting the URL.
| Property | Description |
|---|---|
is_active | Toggle webhook on/off. Inactive webhooks reject all triggers. |
is_public | Published for subscription by other users. |
is_invite_only | Only users whose TradoConnectID is in the invite list can subscribe. |
welcome_message | Message shown to subscribers when they join. |
is_listed | Listed on the public marketplace for discovery. |
1. Create — Private webhook with unique URL
2. Deploy Accounts — Attach broker accounts with multiplier
3. Publish (optional) — Make public/invite-only with welcome message
4. List on Marketplace ( subject to review ) — Make discoverable on the marketplace. This requires admin approval to ensure quality control.
5. Share — Share access code for subscribers to join
The platform includes an in-memory cooldown service that filters identical payloads fired in rapid succession. The default cooldown window is 500ms — identical payloads within this window are automatically filtered.
The cooldown is a safety net, not a guarantee. It only catches repeats that arrive almost at the same time and are handled by the same server, and it forgets them once the window has passed. To make sure a request is carried out at most once, add an idempotency_key to the payload — see Idempotency.
Cooldown service can be turned off for any HFT strategy. This can be done by using the Cooldown Protection toggle in the Edit Webhook Modal.
The webhook accepts JSON payloads. The platform supports three parser formats detected automatically.
1. CoinDCX Futures Parser — Detected via AT: "COINDCXFUTURE"
2. CoinDCX Spot Parser — Detected for CoinDCX spot format
3. Standard Parser — Fallback for payloads using the action field
{
"action": "ENTRY",
"symbol": "B-BTC_USDT",
"side": "BUY",
"quantity": 0.001,
"price": 0,
"order_type": "market_order",
"leverage": 10,
"margin_mode": "ISOLATED",
"take_profit_price": 105000,
"stop_loss_price": 95000,
"margin_currency_short_name": "USDT"
}{
"AT": "COINDCXFUTURE",
"ID": "ENTRY",
"S": "B-BTC_USDT",
"SD": "BUY",
"Q": 0.001,
"P": 0,
"LVG": 10,
"MT": "ISOLATED",
"TP": 105000,
"SL": 95000,
"margin_currency_short_name": "USDT"
}| Field | Aliases | Type | Description |
|---|---|---|---|
at | AT | string | Where to perform (e.g., COINDCXFUTURES) |
action | ACTION, ID | string | Action to perform (see Supported Actions) |
symbol | SYMBOL, S, PAIR | string | Trading pair (e.g., B-BTC_USDT) |
side | SIDE, SD | string | BUY / SELL / B / S / LONG / SHORT |
quantity | Q, QTY | number | Base quantity (before multiplier) |
price | P, PRICE | number | Limit price (0 or omit for market order) |
leverage | LVG, LEVERAGE | number | Leverage multiplier |
order_type | ORDER_TYPE | string | market_order / limit_order |
stepqty | step_qty, STEPQTY | number | Quantity rounding increment (auto-fetched if not provided) |
price_step | STEP | number | Price rounding increment (auto-fetched if not provided) |
percent | PERCENT | number | Exit percentage (1–100) for PARTIAL_EXIT |
qty_type | QTY_TYPE | string | Dynamic sizing mode: CAPITAL_PCT, MARGIN_PCT, RISK_PCT, SIZE_AMOUNT, or MARGIN_AMOUNT. See Quantity Modes below. When set, quantity holds the percentage (for *_PCT) or the amount in the margin currency (for *_AMOUNT). |
margin_basis | MARGIN_BASIS | string | What a MARGIN_PCT percentage is taken of: AVAILABLE (default) or EQUITY (available balance + margin locked in open positions). Only valid with qty_type: MARGIN_PCT. See Margin basis below. |
max_margin_pct | MAX_MARGIN_PCT | number | Cap on the margin a RISK_PCT entry may lock, as a percentage of equity (above 0, up to 100). Only valid with qty_type: RISK_PCT. See Margin cap below. |
take_profit_price | TP | number | Take profit value (price, percentage, or points based on mode) |
stop_loss_price | SL | number | Stop loss value (price, percentage, or points based on mode) |
sl_tp_mode | SL_TP_MODE | string | Combined SL/TP mode: PRICE (default) / PERCENTAGE / POINTS |
sl_mode | SL_MODE | string | SL mode override: PRICE / PERCENTAGE / POINTS |
tp_mode | TP_MODE | string | TP mode override: PRICE / PERCENTAGE / POINTS |
TP_LEVELS | tp_levels | array | Multi-target take-profit ladder (max 10 rungs). Each element is { percent, price }. ENTRY and the FLIP re-entry only — see Multi-Target Take Profit below. |
TP_LIMIT | tp_limit | boolean | Default false. When true, every TP_LEVELS rung is placed as take_profit_limit instead of take_profit_market. |
stop_price | — | number | Stop/trigger price for stop orders |
time_in_force | — | string | good_till_cancel / fill_or_kill / immediate_or_cancel |
reduce_only | REDUCE_ONLY | boolean | Only reduce existing position |
margin_mode | MT | string | ISOLATED / I / CROSS / C — Only applies to ENTRY. Ignored for EXIT/CLOSE actions (uses position's existing mode). |
margin_currency_short_name | margin_currency | string | USDT / INR — Defaults to INR if not provided |
binance_symbol | BINANCE_SYMBOL | boolean | Default false. When true, the symbol/pair is treated as Binance futures notation (e.g. NEWTUSDT.P) and automatically converted to CoinDCX format (e.g. B-NEWT_USDT). |
complete_exit_before_flip | — | boolean | Default false. FLIP only — when true, the existing position is fully closed (via a complete position exit) before the new entry, ignoring the deployment multiplier for the exit leg. |
idempotency_key | IDEMPOTENCY_KEY | string | Optional. Makes the request execute at most once on this webhook: a repeat with the same key is answered duplicate and places nothing. 1–128 characters from A–Z a–z 0–9 . _ : -. See Idempotency. |
Default Margin Currency
If margin_currency_short_name is not provided in the payload, the action will be executed on the INR margin by default. This applies to all actions including ENTRY, EXIT, PARTIAL_EXIT, CLOSE_SYMBOL, UPDATE_SLTP, CANCEL_ALL_ORDERS, and FLIP. Always specify margin_currency_short_name: "USDT" if you are trading on USDT margin pairs. The one exception is an ENTRY or FLIP with qty_type: RISK_PCT, which must state the margin currency: without it the payload is rejected.
The final executed quantity is calculated as:
Final Quantity = roundUp(quantity × multiplier, stepqty)
// Dynamic sizing modes (set via qty_type):
CAPITAL_PCT: quantity = (available_balance × percent / 100) / pricePerContract
MARGIN_PCT: quantity = (available_balance × percent / 100) / (1/leverage + fee) / pricePerContract
RISK_PCT: quantity = (equity × percent / 100 / stopDistance) / pricePerContract
equity = balance + locked margin (unrealised P&L excluded)
stopDistance = |reference − stop| / reference
SIZE_AMOUNT: quantity = amount / pricePerContract
MARGIN_AMOUNT: quantity = amount / (1/leverage + fee) / pricePerContract
// fee = taker fee (GST-inclusive for INR), so initial margin + entry fee
// equals the margin you asked for. Every size is capped to CoinDCX's
// position limit for the chosen leverage.
// MARGIN_PCT with margin_basis EQUITY (default AVAILABLE): the percentage is
// taken of equity (available + margin locked in open positions), never more
// than the available balance:
// margin = min(equity × percent / 100, available_balance)
// RISK_PCT ignores the multiplier and rounds down to the lot step. With
// max_margin_pct the notional is also capped so that margin plus fee is at
// most equity × max_margin_pct / 100, before the available-balance clamp.qty_type)By default quantity is an absolute amount that the deployment multiplier is applied to. Set qty_type to size each account dynamically instead. The percentage modes use the account's own balance; the amount modes use a fixed value in the margin currency (INR/USDT).
| qty_type | quantity means | Calculation |
|---|---|---|
CAPITAL_PCT | % of available balance (1–100) | notional = balance × pct / 100 |
MARGIN_PCT | % of available balance as margin (1–100) | notional = balance × pct / 100 ÷ (1/leverage + fee) |
RISK_PCT | % of account equity to lose if the stop is hit (above 0, up to 100, typically 0.5–5). Needs stop_loss_price on the same payload. | notional = equity × pct / 100 / stopDistance, capped by max_margin_pct (if set) and by what the available balance can margin |
SIZE_AMOUNT | Fixed position size in margin currency | notional = amount |
MARGIN_AMOUNT | Fixed margin amount in margin currency | notional = amount ÷ (1/leverage + fee) |
💡 Amount modes (SIZE_AMOUNT / MARGIN_AMOUNT)
For SIZE_AMOUNT and MARGIN_AMOUNT, quantity is the absolute amount in the margin currency (not a percentage). It follows whatever margin_currency_short_name you set (INR or USDT). For MARGIN_AMOUNT the entry fee is reserved out of the amount, so margin plus fee never exceeds what you entered; sizes are also capped to the leverage tier CoinDCX allows. leverage is required for MARGIN_AMOUNT.
Example — fixed ₹10,000 position size:
{
"AT": "COINDCXFUTURE",
"action": "ENTRY",
"symbol": "B-BTC_USDT",
"side": "BUY",
"quantity": 10000,
"qty_type": "SIZE_AMOUNT",
"leverage": 10,
"margin_currency_short_name": "INR"
}Example — fixed ₹5,000 margin at 5×:
{
"AT": "COINDCXFUTURE",
"action": "ENTRY",
"symbol": "B-ETH_USDT",
"side": "SELL",
"quantity": 5000,
"qty_type": "MARGIN_AMOUNT",
"leverage": 5,
"margin_currency_short_name": "INR"
}Margin basis (margin_basis, MARGIN_PCT only)
MARGIN_PCT takes its percentage of the account's available balance: the wallet minus the margin already locked in open positions. With several positions open, each later entry is therefore smaller than one sized from total equity. Set margin_basis to EQUITY to take the percentage of available balance + locked margin instead; the margin is still capped at the available balance, because only the free part can be margined. AVAILABLE is the default and is the same as leaving the field out. The value is case-insensitive; EQUITY with any other qty_type, or any other value, is rejected rather than ignored.
Example: ₹100,000 wallet, ₹40,000 locked in open positions (₹60,000 available), quantity 10. AVAILABLE gives a margin budget of ₹6,000; EQUITY gives ₹10,000.
Example — 10% of equity as margin at 10×:
{
"AT": "COINDCXFUTURE",
"action": "ENTRY",
"symbol": "B-ETH_USDT",
"side": "SELL",
"quantity": 10,
"qty_type": "MARGIN_PCT",
"margin_basis": "EQUITY",
"leverage": 10,
"margin_currency_short_name": "INR"
}Risk-based sizing (RISK_PCT)
For strategies that define size by risk per trade. quantity is the percentage of the futures wallet's equity to lose if the stop fills. Equity is the balance plus the margin locked in open positions; it excludes unrealised P&L. The eleventh position of a basket is therefore sized from the same base as the first. CAPITAL_PCT uses the available balance; MARGIN_PCT the available balance, or equity with margin_basis EQUITY.
The stop distance is measured against the limit price when one is set, otherwise the live price when the order is sized: PERCENTAGE stops give it directly, PRICE and POINTS stops are divided by the reference. An omitted sl_tp_mode means PRICE. Leverage does not change the size the risk calls for; it sets the margin that size locks, which is what max_margin_pct and the available balance cap. An ENTRY or FLIP with RISK_PCT needs both stop_loss_price and margin_currency_short_name (the INR default does not apply to this mode); without either it is answered HTTP 400 with the reason before anything is queued. See Response below.
The risk is approximate: fees, stop slippage and gaps can make the loss larger, and the stop must sit inside the liquidation distance at the chosen leverage. The quantity is rounded down to the lot step, so rounding never adds risk. Guard rails: a stop closer than 0.1% of the entry (or five times the fee rate) is refused; an account whose minimum order size would risk more than 1.5× the requested amount is refused with the numbers rather than oversized; a deployment with invert_signal is refused for RISK_PCT entries. The deployment multiplier is not applied. COINDCXFUTURE payloads only: a spot or standard payload naming RISK_PCT on an ENTRY or FLIP is rejected (exits are never refused). RISK_PCT is not available for copy groups, and Chase / Edit (replacing a linked order) cannot re-size by it: that request is answered 400 "RISK_PCT cannot re-size a linked order". A FLIP sizes its re-entry after the exit leg has closed: the wallet is read again, so equity then includes the closed position's realised P&L and fees. Each sized order records a sizing block with the order (equity, risk amount, stop distance, requested and placed notional, whether the available balance, max_margin_pct or the leverage tier decided the size, whether the minimum lot had to exceed the margin cap (min_lot_over_cap), and the risk the final quantity actually carries), kept with the order log. Example: ETH at 2000, equity ₹300,000, quantity: 2, stop 1940 (3% away) → risk ₹6,000, notional ₹200,000.
Example — risking 2% of equity with a stop 3% away:
{
"AT": "COINDCXFUTURE",
"action": "ENTRY",
"pair": "ETHUSDT",
"binance_symbol": true,
"side": "BUY",
"qty": 2,
"qty_type": "RISK_PCT",
"leverage": 5,
"margin_mode": "ISOLATED",
"margin_currency_short_name": "INR",
"stop_loss_price": 1940,
"take_profit_price": 2180,
"sl_tp_mode": "PRICE"
}Margin cap (max_margin_pct, RISK_PCT only)
A tight stop makes a risk-based position large: risking 2% on a stop 1.1% away is a notional of about 1.8× equity. max_margin_pct caps the margin such a position may lock, as a percentage of equity, so several open positions cannot over-commit the wallet. The size is reduced until its margin plus entry fee is at most equity × max_margin_pct / 100, so the risk it then carries is smaller than requested. A basket that allows N open positions can use 100 ÷ N (16.66 for six). The cap is applied before the available-balance and leverage-tier limits, which can still reduce the size further.
The cap is a bound except for the exchange's minimum lot. If even one minimum lot needs more margin plus fee than the cap, a minimum lot whose margin plus fee is up to 1.5× the cap is still placed (the order's sizing block flags it as min_lot_over_cap); beyond 1.5× the cap the entry is refused, with both numbers in the reason.
max_margin_pct (alias MAX_MARGIN_PCT) is a number above 0 and at most 100, or a numeric string. It is read on ENTRY and FLIP only and ignored on every other action, so an exit is never refused over it. It is valid only with qty_type: RISK_PCT: with any other qty_type, or a value outside that range, the payload is rejected rather than ignored (answered HTTP 400; see Response below).
Example: equity 10,000, quantity 2 (risk 200), stop 1.1% away, 10× leverage. The risk calls for a notional of about 18,180, which needs about 1,829 of margin plus fee (at a 0.059% fee): 18.3% of equity. With max_margin_pct 16.66 the margin is held to 1,666, the notional to about 16,560, and the risk actually carried to about 182 (1.82% of equity).
Example — risking 2% of equity, margin capped at 16.66%:
{
"AT": "COINDCXFUTURE",
"action": "ENTRY",
"pair": "ETHUSDT",
"binance_symbol": true,
"side": "BUY",
"qty": 2,
"qty_type": "RISK_PCT",
"max_margin_pct": 16.66,
"leverage": 10,
"margin_mode": "ISOLATED",
"margin_currency_short_name": "INR",
"stop_loss_price": 1978,
"take_profit_price": 2044,
"sl_tp_mode": "PRICE"
}Entries are placed one at a time per account
Entries sized by RISK_PCT, or by MARGIN_PCT with margin_basis EQUITY, are placed one at a time per account and margin currency (each waits at most 15 s, then goes ahead anyway; other accounts are never delayed). Each entry is sized after the previous one has taken its margin, so a burst of signals on one account is sized against the free balance the account will really have. Other sizing modes, and every exit, are never held back.
Auto Instrument Specs
If stepqty or price_step is not provided in the payload, the platform try to automatically fetches instrument specifications from the broker/exchange to ensure proper rounding.
Every deployed account (and every copy-group member) carries its own multiplier. It scales absolute quantities only. Anything already expressed as a percentage of the account's own balance or its own live position is left untouched, because that value is per-account by definition and scaling it again would double-count the multiplier.
| Action / mode | Multiplier | What each account gets |
|---|---|---|
ENTRY / FLIP re-entry with absolute quantity | Applied | quantity × multiplier, rounded to the instrument step |
qty_type: SIZE_AMOUNT / MARGIN_AMOUNT | Applied | The amount is a fixed figure shared by every account, so the resolved quantity is scaled |
qty_type: CAPITAL_PCT / MARGIN_PCT / RISK_PCT | Not applied | Sized from each account's own balance (RISK_PCT: its own equity) — 10% means 10% on every account |
PARTIAL_EXIT with percent | Not applied | percent of that account's live position — 50% closes half on every account |
PARTIAL_EXIT with absolute quantity | Applied | quantity × multiplier, capped at the live position |
| EXIT with a quantity | Applied | quantity × multiplier, capped at the live position |
| CLOSE_SYMBOL / CLOSE_ALL | Not applied | The whole live position is closed, whatever its size |
FLIP exit leg with complete_exit_before_flip: true | Not applied | Full position exit, so the account is guaranteed flat before the re-entry |
TP_LEVELS rungs | Quantity only | Rung quantities follow the scaled order quantity; trigger prices are identical on every account |
| UPDATE_SLTP / CANCEL_ALL_ORDERS / SKIP | N/A | No quantity is involved |
💡 Rule of thumb
A percentage is never multiplied. A quantity or a fixed amount always is. So a 50% partial exit closes half the position on a 1× account and half the position on a 3× account — the 3× account simply has a larger position to take half of.
TP_LEVELS)CoinDCX Futures allows only one full-position take profit and one full-position stop loss per position — those are take_profit_price and stop_loss_price. To scale out at several targets, add TP_LEVELS. Each rung becomes its own take_profit_market (default) or take_profit_limit order sized to a percentage of the order quantity.
These order types are position-linked on CoinDCX, so they are cancelled automatically when the position closes or when its stop triggers. No stale target is left behind to re-open a reverse position. TP_LEVELS is honoured for ENTRY and for the re-entry leg of FLIP only — every other action ignores it.
| Field | Type | Required | Description |
|---|---|---|---|
TP_LEVELS | array | No | Ordered ladder — element 1 is TP1, element 2 is TP2, and so on. Maximum 10 rungs. Accepted as a JSON array or as a JSON string (handy for plain-text TradingView alerts). |
TP_LEVELS[].percent | number | Yes | Percentage of the order quantity to close at this rung. All percentages must sum to 100 or less. |
TP_LEVELS[].price | string / number | Yes | Target as an absolute price (1234), a percentage ("10%") or points ("10point"). |
TP_LIMIT | boolean | No | true places every rung as take_profit_limit. Default false (take_profit_market). |
time_in_force | string | No | Applies to take_profit_limit rungs only: good_till_cancel (default), immediate_or_cancel, fill_or_kill. Omitted for market rungs, as CoinDCX requires. |
Price notation
price resolves against the same reference price used by take_profit_price / stop_loss_price — the limit price when one is set, otherwise the live market price — and always moves in the position's profit direction.
| Notation | BUY (long) | SELL (short) |
|---|---|---|
"1234" | 1234 | 1234 |
"10%" | ref × 1.10 | ref × 0.90 |
"10point" | ref + 10 | ref − 10 |
Example — 70% at +10 points, 30% at +10%:
{
"AT": "COINDCXFUTURE",
"ID": "ENTRY",
"SYMBOL": "B-ETH_USDT",
"SIDE": "BUY",
"QTY": 700,
"qty_type": "SIZE_AMOUNT",
"LVG": 25,
"margin_currency_short_name": "INR",
"stop_loss_price": 10,
"take_profit_price": 10,
"sl_tp_mode": "PERCENTAGE",
"TP_LEVELS": [
{ "percent": 70, "price": "10point" },
{ "percent": 30, "price": "10%" }
]
}With a reference price of 2000 and SIZE_AMOUNT 700 resolving to a quantity of 800:
| Leg | Order | Quantity | Trigger |
|---|---|---|---|
| Entry | market buy, full-position SL 1800 + TP 2200 | 800 | — |
| TP1 | take_profit_market sell | 560 (70%) | 2010 |
| TP2 | take_profit_market sell | 239 (30%, trimmed) | 2200 |
Behaviour notes
• Rung quantities always sum to less than the order quantity. One step size is reserved so the full-position TP/SL keeps a residual to close — that is why TP2 above is 239 and not 240.
• Multipliers scale quantity only. A 3× deployment gets rungs of 1680 / 719 at the same trigger prices, because prices come from the reference price, not from quantity.
• Prices and quantities are rounded to the instrument's price_increment and quantity_increment. A rung that rounds below min_quantity is skipped and logged instead of being rejected by the broker.
• Limit rungs get a trigger offset. For take_profit_market the price you give is the trigger. For take_profit_limit your price is the limit and the trigger sits 0.5% beyond it, to satisfy CoinDCX's ordering rules.
• Rungs are placed one second after the entry. The entry has to register as an open position first, otherwise CoinDCX books the targets as fresh risk and locks extra margin. Triggers are processed asynchronously, so this costs no webhook latency.
• A malformed ladder rejects the webhook before any order is placed, so a position is never opened without the targets you asked for.
• Rungs never fail the entry. Once the entry is accepted, a rung the broker rejects is recorded on its own TP_LEVEL order log and the entry still reports SUCCESS. Each rung appears as its own order log with type TP_LEVEL.
If your alert source sends symbols in Binance futures notation, set binance_symbol: true and the platform converts the symbol to CoinDCX format automatically before execution.
| Binance notation | Converted (CoinDCX) |
|---|---|
NEWTUSDT.P | B-NEWT_USDT |
RAREUSDT.P | B-RARE_USDT |
ETHUSDC.P | B-ETH_USDC |
{
"AT": "COINDCXFUTURE",
"action": "ENTRY",
"symbol": "NEWTUSDT.P",
"side": "BUY",
"quantity": 100,
"binance_symbol": true,
"margin_currency_short_name": "USDT"
}Requests get repeated: a call times out and you cannot tell whether Connect received it, an alert fires twice, a script retries. Add an idempotency_key to the payload and Connect carries that key out at most once on the webhook. Every later request with the same key is answered 202 with "status": "duplicate", and nothing is placed.
{
"action": "ENTRY",
"symbol": "B-ETH_USDT",
"side": "BUY",
"quantity": 0.012,
"margin_currency_short_name": "USDT",
"idempotency_key": "strategy-7.2026-10-16T09:00:00Z.entry"
}The rules
• Field. idempotency_key; the name is not case-sensitive, so IDEMPOTENCY_KEY works too. It is read in all three payload formats and on every action: ENTRY, EXIT, PARTIAL_EXIT, CLOSE_SYMBOL, CLOSE_ALL, UPDATE_SLTP, CANCEL_ALL, FLIP and SKIP. Other fields such as signal_id are not read as a key. There is no Idempotency-Key header, because TradingView cannot set headers.
• Value. Text of 1 to 128 characters from A–Z a–z 0–9 . _ : -; spaces around it are trimmed. A whole number below 9,007,199,254,740,992 is also accepted and treated as text. Anything else (empty, longer than 128 characters, other characters, true/false, an object) makes the payload invalid, and it is answered like any other invalid payload (see Response). With the idempotency switch off, an invalid key is ignored, not rejected: the request is handled as if it had no key.
• Scope. One webhook. The same key sent to another webhook is a different request.
• One key, one payload. Connect stores a fingerprint of what the request does: the action, symbol, side, size and sizing mode, price, percent, leverage, margin settings (mode and currency), order type, stops and take-profit targets. The key itself, the raw payload, labels, comments and timestamps are not part of it, so a retry that differs only in such noise is a plain repeat. Sending the key again with a different fingerprint (another symbol, side or quantity, say) is a mistake in the key, and is answered 422 with IDEMPOTENCY_KEY_PAYLOAD_MISMATCH. Nothing is placed, and nothing is passed on to a group's members.
• Window. A used key stays a duplicate for 48 hours after the request that executed it. After that, the same key is treated as new.
• At most once. The first request with a key executes. Any later request with the same key is a duplicate, even while the first is still being processed, and even if the first failed or was refused on every account. To try again, send a new key.
• One exception: nothing reached the broker. If Connect can show that the first request never got as far as the broker (trading was halted, the payload could not be processed, the webhook was deactivated or could not be read just after the request arrived), the key is released and a resend with the same key executes. The Read-back API tells you which case you are in (same_key_retry).
• No record for an inactive or unknown webhook. A request to a webhook that is inactive or unknown when it arrives is answered 202 and places nothing. Its key is not recorded, so a later request with the same key is not a duplicate, and the Read-back API has no entry for it.
• Checked before the cooldown. A rapid repeat with the same key is a duplicate, not a cooldown skip.
• In the logs. Each repeated request is recorded as one log row with status DUPLICATE. It executed nothing and is not counted in the trigger totals. The row names the request that owns the key. On a group member's webhook it can also carry duplicate_payload_mismatch: true: the member saw a repeat whose payload differs from the original's.
Which key to send
| Your situation | Send |
|---|---|
| You do not know whether the first attempt arrived (timeout, 5xx, connection reset) | The same key. If the first attempt arrived, you get duplicate; if it did not, this one executes. |
| A deliberate second attempt, after the read-back shows the first was refused or failed | A new key. |
| A new signal or a different order | A new key. |
TradingView
Build the key from {{strategy.order.id}} and {{timenow}}, for example "idempotency_key": "{{strategy.order.id}}-{{timenow}}". Each alert gets its own key, and a re-delivery of the same alert shares it. Without {{timenow}}, two different alerts can share a key, and the second is answered 422 or duplicate and never placed. Keep order ids to the characters listed above: a space makes the payload invalid.
What happens when
| Case | Result |
|---|---|
| Two identical requests arrive at the same moment | One executes. The other is a duplicate. |
| A repeat arrives while the original is still being processed | Duplicate. It is never placed twice. |
| The original failed, or was refused on every account | Duplicate: the key is used. Send a new key to try again. |
| The original never reached the broker (trading halted, payload could not be processed, webhook deactivated just after the request arrived) | The key is released. A resend with the same key executes. |
| The webhook is inactive or unknown when the request arrives | Answered 202 with nothing placed and nothing recorded. The key is not used, and the read-back has no entry for it. |
| The key is sent again with a different payload | Answered 422 IDEMPOTENCY_KEY_PAYLOAD_MISMATCH. Nothing is placed. Send a new key for a new order. |
| Connect stopped after accepting the request but before sending anything to the broker | The key is freed after 2 to 3 minutes. A resend then executes. |
| Connect stopped after orders had started going out | The key stays used. Check the read-back for the outcome. |
| Connect could not record the key | The request is answered 503 and nothing is executed. Retry with the same key. If the key had in fact been saved, a retry may answer duplicate for up to 3 minutes. Nothing is placed twice. |
| A group member's key cannot be recorded | That member executes nothing, and its webhook log is FAILED with the reason CLAIM_ERROR. Resend with the same key. |
| The key is older than 48 hours | It is treated as new, and the request executes. |
The idempotency switch is off (the response says "idempotency": "disabled") | A valid key is stored on the log but not enforced, and the read-back has no entry for it. An invalid key is ignored, not rejected. Do not rely on the key to prevent repeats. |
A POST to the webhook URL is answered 202 Accepted once the payload is received; the orders are placed afterwards, in the background. The 202 means "received", not "every account was filled": each account's outcome (see Execution Results under Manual Trading Actions) is in the webhook log, and for a request that carried an idempotency key also in the Read-back API. A repeated key is also answered 202, so callers that only look at the status code keep working. TradingView ignores the body.
Example — accepted:
{
"success": true,
"status": "accepted",
"message": "Webhook trigger accepted for processing",
"request_id": "9b0d3f3e-5d0a-4a57-9a43-2f1f7c1a6b10",
"client_request_id": "01K7A1Q2.01K7A1Q3.entry.0:1",
"idempotency_key": "01K7A1Q2.01K7A1Q3.entry.0",
"idempotency": "enforced"
}| Field | Present | Meaning |
|---|---|---|
status | Every answer except a 400 | accepted, duplicate (see below), unavailable (a 503) or error (a 422). |
request_id | Always | Connect's id for this request. It is unique per request, and shown as Request ID in the logs. |
client_request_id | When you sent a valid X-Request-Id | Your own id, echoed back. |
idempotency_key | When the payload carried a key | The key, as Connect read it. |
idempotency | When the payload carried a key | enforced, or disabled when the platform is not enforcing keys. In that case do not retry an ENTRY: a repeat would execute again. |
Example — duplicate (also 202; nothing was placed again):
{
"success": true,
"status": "duplicate",
"message": "Duplicate idempotency_key: this webhook already received it; nothing was placed again",
"request_id": "5c1e8a47-92b0-4d3f-a6c5-1e7f0b2d9a38",
"client_request_id": "01K7A1Q2.01K7A1Q3.entry.0:2",
"idempotency_key": "01K7A1Q2.01K7A1Q3.entry.0",
"idempotency": "enforced",
"original_request_id": "9b0d3f3e-5d0a-4a57-9a43-2f1f7c1a6b10",
"original_received_at": "2026-10-16T09:00:01.123456Z",
"original_committed": true
}| Field | Meaning |
|---|---|
original_request_id, original_received_at | The request that owns the key, and when it arrived. |
original_committed | true: the original has started executing or has executed. false: it has not started yet. It can still be released without executing (for example because trading is halted), and then this repeat has placed nothing either. When it is false, confirm the original in the Read-back API: same_key_retry: "execute" means the key is free, so send the same key again. |
The answer is duplicate whether the original is still being processed or has finished. The original's outcome is in the Read-back API.
Example — the key could not be recorded (503, with a Retry-After: 1 header). Only requests that carry a key can get this:
{
"success": false,
"status": "unavailable",
"error": "The idempotency key could not be recorded, so nothing was executed. Retry with the same key.",
"request_id": "2f6b0c9d-4a1e-47d8-b3a5-8e9c7d1f4a62",
"idempotency_key": "01K7A1Q2.01K7A1Q3.entry.0"
}Example — the key was already used with a different payload (422; nothing was placed):
{
"success": false,
"status": "error",
"error": "IDEMPOTENCY_KEY_PAYLOAD_MISMATCH",
"message": "This idempotency_key was already used for a different request on this webhook, so nothing was placed. Use a new key for a new request.",
"request_id": "4a7d9e12-6c3b-4f58-a0e1-3b8c5d2f9a71",
"client_request_id": "01K7A1Q2.01K7A1Q3.entry.1:1",
"idempotency_key": "01K7A1Q2.01K7A1Q3.entry.0",
"original_request_id": "9b0d3f3e-5d0a-4a57-9a43-2f1f7c1a6b10"
}| Field | Meaning |
|---|---|
error, message | error is the code to test for; message is a sentence for people and may be reworded. |
original_request_id | The request id of the request that first used the key. |
Request ids
• request_id is minted by Connect for every request and is never reused.
• X-Request-Id is an optional header you can add to tag a request with your own id (1–128 characters from A–Z a–z 0–9 . _ : -). Connect echoes it as client_request_id, stores it on the log row, and shows it in the logs. A value in another format is ignored, not rejected. It does not need to be unique: retries can reuse it. Putting the attempt number in it, such as my-signal-42:2, tells attempts apart.
• The X-Request-ID response header is for Connect's own access logs and is a different value. Use the request_id in the body.
Other answers
• Invalid payload. A payload that is valid JSON but that Connect cannot parse — for example RISK_PCT without stop_loss_price, max_margin_pct with another qty_type, an unknown margin_basis, or an invalid idempotency_key (while the idempotency switch is off an invalid key is ignored instead) — is answered 400 Bad Request with the reason, before anything is queued. This is on by default; if the operator turns it off, such a payload is answered 202 and rejected in the background, and the reason appears only in Connect's server logs. A body that is not valid JSON is answered 400 either way. Every 400 body carries request_id (and client_request_id when you sent a valid header).
• Unknown or inactive webhook. Still 202, with the same body as a first accepted request, so a URL cannot be probed to find out which webhooks exist. A request with a key to such a webhook records nothing. Reading is different: the Read-back API answers 404 for an unknown URL.
Example — body of a 400 response (a RISK_PCT entry without a stop):
{
"success": false,
"error": "Invalid payload",
"details": "missing required field: stop_loss_price is required for RISK_PCT sizing",
"request_id": "7d2a5c1e-3b4f-4e0a-8f6d-2c9b1a7e5d30"
}Aliases: PLACE_ORDER, BUY, SELL, LONG, SHORT, OPEN
Required: symbol, side, quantity
Opens a new position or adds to an existing one. Supports market and limit orders, leverage setting, take-profit/stop-loss (with price, percentage, or points mode), and margin mode configuration.
Price-based SL/TP (default):
{
"AT": "COINDCXFUTURE",
"action": "ENTRY",
"symbol": "B-BTC_USDT",
"side": "BUY",
"quantity": 0.001,
"leverage": 10,
"margin_mode": "CROSS",
"take_profit_price": 105000,
"stop_loss_price": 95000,
"margin_currency_short_name": "INR"
}Percentage-based SL/TP:
{
"AT": "COINDCXFUTURE",
"action": "ENTRY",
"symbol": "B-BTC_USDT",
"side": "BUY",
"quantity": 0.001,
"leverage": 10,
"stop_loss_price": 2,
"take_profit_price": 5,
"sl_tp_mode": "PERCENTAGE",
"margin_currency_short_name": "INR"
}SL = 2% below entry, TP = 5% above entry (inverted for SELL)
Points-based SL/TP:
{
"AT": "COINDCXFUTURE",
"action": "ENTRY",
"symbol": "B-BTC_USDT",
"side": "BUY",
"quantity": 0.001,
"leverage": 10,
"stop_loss_price": 500,
"take_profit_price": 1000,
"sl_tp_mode": "POINTS",
"margin_currency_short_name": "INR"
}SL = 500 points below entry, TP = 1000 points above entry (inverted for SELL)
💡 SL/TP Mode Priority
Use sl_tp_mode to set both SL and TP to the same mode. Use sl_mode / tp_mode individually to override. Individual modes take precedence over the combined mode. For EXIT actions, margin_mode is ignored — the position's existing margin type is used.
Multiple take-profit targets (TP_LEVELS):
{
"AT": "COINDCXFUTURE",
"action": "ENTRY",
"symbol": "B-ETH_USDT",
"side": "BUY",
"quantity": 800,
"leverage": 25,
"stop_loss_price": 10,
"take_profit_price": 10,
"sl_tp_mode": "PERCENTAGE",
"TP_LEVELS": [
{ "percent": 70, "price": "10point" },
{ "percent": 30, "price": "10%" }
],
"margin_currency_short_name": "INR"
}Scales out at two targets alongside the full-position SL/TP. See Multi-Target Take Profit under Payload & Syntax for the full rules.
Required: symbol, side
Closes the full position for a given symbol. The side is auto-detected from the current position. Sets reduce_only: true automatically.
{
"AT": "COINDCXFUTURE",
"action": "EXIT",
"symbol": "B-BTC_USDT",
"side": "SELL",
"quantity": 0.001,
"leverage": 10,
"stepqty": 0.001,
"step": 0.1,
"margin_currency_short_name": "INR"
}Aliases: PARTIAL, PARTIAL_CLOSE
Required: symbol, percent (1–100)
Optional: margin_currency_short_name — defaults to INR if not provided
Closes a specified percentage of the current position. Useful for scaling out of positions gradually.
The percentage is measured against each account's own live position and the deployment multiplier is not applied to it. A percent of 50 closes half the position on every account, whatever its multiplier. Send an absolute quantity instead if you do want the multiplier applied.
{
"AT": "COINDCXFUTURE",
"action": "PARTIAL_EXIT",
"symbol": "B-BTC_USDT",
"percent": 50,
"margin_currency_short_name": "USDT"
}Aliases: REVERSE, SWITCH
Required: symbol, side, quantity
Optional: margin_currency_short_name — defaults to INR if not provided. If no margin is specified, the exit leg automatically uses the position's margin currency.
Optional: complete_exit_before_flip (boolean, default false)
Closes the existing position and opens a new one in the opposite direction. Executed as two operations: close existing + open new.
{
"AT": "COINDCXFUTURE",
"action": "FLIP",
"symbol": "B-BTC_USDT",
"side": "SELL",
"quantity": 0.001,
"leverage": 10,
"margin_currency_short_name": "USDT"
}💡 complete_exit_before_flip
By default the exit leg places a multiplier-scaled reduce-only order. Set complete_exit_before_flip: true to fully close the entire open position (regardless of multiplier) before opening the new entry — guaranteeing a flat position first.
{
"AT": "COINDCXFUTURE",
"action": "FLIP",
"symbol": "B-BTC_USDT",
"side": "SELL",
"quantity": 0.001,
"leverage": 10,
"complete_exit_before_flip": true,
"margin_currency_short_name": "USDT"
}Aliases: CLOSE_POSITION, CLOSE, EXIT_POSITION, EXIT_SYMBOL, EXIT_ALL_POSITION
Required: symbol
Optional: margin_currency_short_name — defaults to INR if not provided
Closes the full open position for a specific trading pair. Only AT, symbol, and margin_currency_short_name are used — all other parameters are ignored.
{
"AT": "COINDCXFUTURE",
"action": "CLOSE_SYMBOL",
"symbol": "B-BTC_USDT",
"margin_currency_short_name": "USDT"
}Aliases: EXIT_ALL, CLOSE_ALL_POSITIONS, CLOSEALL
Emergency action that closes every open position on all deployed accounts. No symbol parameters required.
Optional: margin_currency_short_name — defaults to INR if not provided. Specify to target positions on a specific margin.
{
"AT": "COINDCXFUTURE",
"action": "CLOSE_ALL",
"margin_currency_short_name": "USDT"
}Aliases: CANCEL, CANCEL_ALL
Cancels all pending/open orders on all deployed accounts.
Optional: margin_currency_short_name — defaults to INR if not provided.
{
"AT": "COINDCXFUTURE",
"action": "CANCEL_ALL_ORDERS",
"margin_currency_short_name": "USDT"
}Aliases: SLTP, SET_SLTP
Required: symbol, at least one of SL or TP
Optional: margin_currency_short_name, SLTPMODE
Updates the stop-loss and/or take-profit prices for an existing position. The broker is determined from the webhook — no AT field required. Supports price, percentage, and points modes via SLTPMODE.
Price mode (default):
{
"action": "UPDATE_SLTP",
"symbol": "B-BTC_USDT",
"TP": 105000,
"SL": 95000,
"margin_currency_short_name": "USDT"
}Percentage mode:
{
"action": "UPDATE_SLTP",
"symbol": "B-BTC_USDT",
"SL": 3,
"TP": 5,
"SLTPMODE": "PERCENTAGE",
"margin_currency_short_name": "USDT"
}SL = 3% from entry price, TP = 5% from entry price (direction based on position side)
Points mode:
{
"action": "UPDATE_SLTP",
"symbol": "B-BTC_USDT",
"SL": 500,
"TP": 1000,
"SLTPMODE": "POINTS",
"margin_currency_short_name": "USDT"
}SL = 500 points from entry, TP = 1000 points from entry (direction based on position side)
Aliases: NOP, NOOP, IGNORE, NO_OP
Does nothing. Useful for conditional logic in your alert scripts where certain conditions should skip execution.
{
"AT": "COINDCXFUTURE",
"action": "SKIP"
}The 202 you get when you send a request only says "received". The read-back API tells you what happened afterwards. For every request that carried an idempotency_key it reports whether the request ran, on which of your accounts and with what outcome, and it lists the positions your accounts hold right now. Use it to confirm that a signal landed, to decide whether a resend is safe, and to reconcile after a timeout.
| Method | Path | Returns |
|---|---|---|
| GET | /webhook/{webhook_url_id}/executions | One entry per idempotency key sent to the webhook in the last 24 hours, with the outcome on each of your accounts. |
| GET | /webhook/{webhook_url_id}/positions | The open positions of your accounts that are deployed on the webhook. |
Base URL https://connect.trado.trade. Both paths also work under /api/webhook/....
Access: the webhook URL id is the credential
As when sending, there is no header and no signature: the URL id itself is the secret. Anyone who has the URL can already trade on the webhook, so reading adds no new exposure. Keep the id out of chat messages and logs (in a shell, keep it in an environment variable). It is never repeated in a response body, and every answer carries Cache-Control: no-store.
curl "https://connect.trado.trade/webhook/$WEBHOOK_URL_ID/executions?since=2026-10-16T09:00:00Z"
Only requests that carried an idempotency_key are listed, in order of arrival. Each entry describes the request that owns the key; repeats of the key are counted in duplicates.
A key has an entry only if Connect recorded it. A key sent to a webhook that was inactive or unknown when the request arrived gets no entry, and neither does a key sent while the idempotency switch was off. A missing entry therefore does not tell you whether the request ran.
| Parameter | Meaning |
|---|---|
since | Start of the first page, as an RFC 3339 time. The default, and the earliest allowed, is 24 hours ago: an older value is moved up, and the answer's since shows the value used. A time in the future or in another format is answered 400 invalid_since. |
cursor | The next_cursor of the previous page. It replaces since. Treat it as opaque; an invalid one is answered 400 invalid_cursor. |
limit | Keys per page: 100 by default, 1 to 500. A page can hold fewer keys than that (see truncated), so keep following next_cursor until it is null. |
Example answer:
{
"as_of": "2026-10-16T09:05:00.512Z",
"since": "2026-10-16T09:00:00Z",
"executions": [
{
"idempotency_key": "01K7A1Q2.01K7A1Q3.entry.0",
"request_id": "9b0d3f3e-5d0a-4a57-9a43-2f1f7c1a6b10",
"client_request_id": "01K7A1Q2.01K7A1Q3.entry.0:1",
"action": "ENTRY",
"symbol": "B-ETH_USDT",
"received_at": "2026-10-16T09:00:01.123Z",
"completed_at": "2026-10-16T09:00:03.020Z",
"status": "filled",
"reason_code": null,
"same_key_retry": "duplicate",
"duplicates": {
"count": 1,
"last_at": "2026-10-16T09:00:06.400Z",
"last_request_id": "5c1e8a47-92b0-4d3f-a6c5-1e7f0b2d9a38"
},
"accounts": [
{
"deployment_ref": "3c1f6a2e-7b0d-4b8e-9d55-0f2a8c1e4d77",
"status": "filled",
"side": "BUY",
"requested_qty": 0.012,
"filled_qty": 0.012,
"avg_price": 2451.37,
"fill_source": "order_response",
"reason_code": null,
"reason": null,
"exchange_code": null,
"warning_code": null,
"legs": 1,
"executed_at": "2026-10-16T09:00:02.871Z"
}
],
"accounts_truncated": false,
"other_accounts": {
"count": 0, "filled": 0, "partial": 0,
"refused": 0, "error": 0, "pending": 0
}
}
],
"truncated": false,
"next_cursor": null
}| Field | Meaning |
|---|---|
as_of, since | When the answer was produced, and the start time it used (see the since parameter). |
truncated, next_cursor | next_cursor is null on the last page; otherwise pass it as cursor to get the next page. truncated is true only when the page ended early because it reached the cap of 2,000 account rows; next_cursor then continues from the first key left out. A page cut only by limit has a next_cursor and is not truncated. |
request_id, client_request_id | Connect's id of the request that owns the key, and its X-Request-Id if it had one. |
received_at, completed_at | When Connect received the request and when it finished processing it. completed_at is null while the status is pending. |
status, reason_code | The outcome of the whole request, and why it was refused or failed. See the tables below. |
same_key_retry | execute: the key was released because nothing reached the broker, so sending the same key again executes. duplicate: the key is used, so a retry needs a new key. |
duplicates | How many later requests were answered duplicate for this key, and the last one. |
accounts | The outcome on each of your own accounts, up to 100 per key (accounts_truncated says if there were more). |
other_accounts | Accounts of other users deployed on a marketplace webhook, counted by status. They are never listed. |
One account entry
| Field | Meaning |
|---|---|
deployment_ref | A random id for that account's deployment on this webhook. It stays the same for the life of the deployment. It is the only way an account is identified. |
status | filled, partial, refused, error or pending; see Statuses. An account that placed several orders (a FLIP exits and then enters) shows one status for all of them, by the rules in that table. |
requested_qty, filled_qty, avg_price | The quantity asked for, and what is known of the fill. filled_qty and avg_price are null when the fill is not known. |
fill_source | exchange_sync: executed quantity and average price taken from the exchange. order_response: taken from the exchange's answer when the order was created. null: not known yet. |
reason_code, reason, exchange_code | Why an order was refused or failed: a code from the table below and a fixed sentence (SKIPPED_UNKNOWN is the one exception). On a partial account they carry the reason of the order that was refused or failed. For EXCHANGE_REJECTED, exchange_code is the exchange's own error code when it is a plain code. The exchange's message text is never returned. |
warning_code | TP_INCOMPLETE: the entry went through but a take-profit level could not be placed. The status is not downgraded for it. |
legs | How many orders this request placed on the account. |
filled means accepted by the exchange
A market order can still be settling when you read it, so filled_qty may be null, or lower than requested_qty (the account then reads partial), for a moment. The true quantities are the positions below. When you check for drift, compare positions, not filled_qty.
| Status | On one account | On the whole request |
|---|---|---|
pending | An order of the account is still being processed. | Not finished (completed_at is null): the key is reserved but execution has not completed, or the request is done but its per-account results are not written yet. Read again. A request whose results are still missing 2 to 3 minutes after it finished becomes error with INTERNAL, and so does one still pending 15 minutes after it arrived. |
filled | Every order of the account filled in full. | Every account filled in full. |
partial | Something executed, but not everything in full: at least one order filled (fully or short of the requested quantity) and at least one did not fill in full (it was refused, it failed with an error, or its fill is short). A FLIP whose exit filled and whose entry was refused has closed the old position and not opened the new one. Check positions. | At least one account filled or partial, and not every account filled in full. |
refused | Nothing was executed for the account: every order was refused. reason_code says why. | Nothing was executed: every account was refused, no account is deployed (NO_DEPLOYMENTS), or the key was released before execution (the reason code is then the release reason; see below). |
error | Nothing filled and at least one order failed with an error: the exchange did not answer in time, or something failed inside Connect. An order may be live, so check positions before you resend. | No account filled or partly executed and at least one failed with an error, or the request failed inside Connect. |
Other users' accounts on a marketplace webhook are counted in other_accounts and also count in the request status.
| Reason code | Status | Meaning |
|---|---|---|
COOLDOWN | refused | An identical payload arrived inside the cooldown window. |
ACCOUNT_INACTIVE | refused | The account is not active. |
OUTSIDE_TRADING_HOURS | refused | Outside the trading hours set for the account. |
NO_POSITION | refused | There was no position to close or reduce. |
HALTED | refused | Trading was halted platform-wide. The order's message in the webhook log starts with Halted: followed by the operator's reason. |
SKIPPED_UNKNOWN | refused | Nothing was placed: Connect skipped the account for a reason it does not recognise. reason carries the order's own text (control characters removed, cut to 200 characters) instead of a fixed sentence. It is returned for your own accounts only. |
BELOW_MIN_SIZE | refused | The size was below the exchange's minimum or lot step. |
INSUFFICIENT_MARGIN | refused | Not enough margin or balance. |
SIZING_REFUSED | refused | The sizing rules refused the order (for example the RISK_PCT checks). |
EXCHANGE_REJECTED | refused | The exchange rejected the order. |
CREDENTIALS | refused | The exchange rejected the account's API key, or Connect could not decrypt it. |
BROKER_TIMEOUT | error | The exchange did not answer in time. The order may be live: check positions before you resend. |
INTERNAL | error | Something failed inside Connect, or the request never finished (see pending above). |
NO_DEPLOYMENTS | refused | Request level only: no account is deployed on the webhook. |
The Status column is the account's status when none of its orders filled. Beside a filled order the account is partial and keeps the code of the order that did not fill.
A request whose key was released shows refused with same_key_retry: "execute" and one of these reasons: PARSE_ERROR, WEBHOOK_INACTIVE (the webhook was deactivated after the request arrived), WEBHOOK_UNAVAILABLE (the webhook could not be read after the key was recorded), BROKER_UNAVAILABLE, LOG_WRITE_FAILED, HALTED, DEPLOYMENTS_UNAVAILABLE, COMMIT_FAILED, CLAIM_ERROR (a group member's key could not be recorded: that member's webhook log is FAILED with this reason) or ORPHANED. Nothing reached the broker in any of them.
The current positions of your active accounts that are deployed on the webhook, read from the exchange and summed per symbol, side and margin currency. They are the accounts' real positions, whatever opened them: an account deployed on two webhooks reports the same positions on both.
{
"as_of": "2026-10-16T09:05:00.512Z",
"cached": false,
"supported": true,
"accounts": { "total": 3, "read": 2, "unreadable": 1, "truncated": false },
"other_accounts": 12,
"positions": [
{
"symbol": "B-ETH_USDT",
"binance_symbol": "ETHUSDT",
"side": "LONG",
"margin_currency": "INR",
"accounts": 2,
"total_qty": 0.024,
"avg_entry_price": 2451.37
}
],
"errors": [ { "code": "BROKER_TIMEOUT", "accounts": 1 } ]
}| Field | Meaning |
|---|---|
cached | true when the answer comes from a short cache (about 4 seconds). Asking faster returns the same data. |
supported | false when the webhook's broker has no futures positions; positions is then empty. |
accounts | How many of your accounts exist on the webhook (total), how many were read, how many could not be read (unreadable) and whether the list was cut at 100 (truncated). An account with no open position counts as read. |
other_accounts | The number of other users' accounts deployed on the webhook. They are never read. |
positions | One entry per symbol, side (LONG or SHORT) and margin currency: how many accounts hold it, total_qty (the sum of quantities) and avg_entry_price (weighted by quantity). binance_symbol is given for USDT pairs. |
errors | Accounts that could not be read, counted by code: CREDENTIALS, BROKER_TIMEOUT or EXCHANGE_ERROR. Only codes with at least one account are listed; the list is empty when every account was read. |
If accounts.unreadable is above zero, the totals do not cover every account. Do not treat them as complete.
| Status | Meaning |
|---|---|
200 | The answer. Also for an inactive webhook, whose earlier keys and open positions can still be read. |
400 | invalid_since or invalid_cursor, in the form {"error": "invalid_since"}. |
404 | {"error": "not_found"}: no webhook has this URL id. |
429 | Too many requests. The Retry-After header says how long to wait. |
500 | {"error": "internal_error"}: reading failed inside Connect. The body has no other detail. Try again after a moment. |
503 | {"error": "readback_disabled"}: reading is switched off. |
Sending to an unknown URL is answered 202, so that a URL cannot be probed and sending stays fast. Reading has to look the webhook up anyway, and an empty 200 for a mistyped URL would read as "nothing executed" and hide the mistake from your reconciliation, so it answers 404.
Only the accounts of the webhook's owner are reported. Other users who deployed accounts on a marketplace webhook appear only as counts in executions, and not at all in positions, because positions include trades from outside the strategy. Account ids, names, client ids, exchange user ids and keys are never returned; an account is identified by its deployment_ref only.
• Rate limits. Executions: 60 requests a minute per webhook. Positions: 30 a minute per webhook. 1,200 a minute per IP address across both. Beyond that you get 429 with Retry-After.
• Size. At most 500 keys and 2,000 account entries per page, 100 of your accounts per key, a 24-hour window. Positions read at most 100 accounts.
• Polling. Ask no more often than every 5 seconds, and only while a key you care about is still pending. Start from the time you sent the oldest unsettled request, minus a minute, as since, and follow next_cursor. Stop polling once every key is final.
Deploying an account to a webhook connects your broker account to receive and execute trade signals from that webhook.
| Setting | Default | Description |
|---|---|---|
multiplier | 1.0 | Scales the base quantity. E.g., multiplier of 2 doubles the position size. Range: 0.01–100. It applies to absolute quantities and fixed-amount sizing only — not to percentage entries (CAPITAL_PCT / MARGIN_PCT) or percentage partial exits. See Where the Multiplier Applies under Payload & Syntax. |
is_active | true | Toggle deployment on/off without removing it. |
You receive email notifications for every deployment-related action:
Account deployed to webhook
Account removed from webhook
Deployment activated
Deployment deactivated
Multiplier updated (shows old → new value)
Notification emails include: webhook name, exchange, account name, client ID, multiplier, status, access type (Private/Public/Listed), who performed the action (Owner/Subscriber), and timestamp.
• Webhook Owner — Can see ALL deployments and perform actions on all accounts
• Subscriber — Can only see and manage their OWN deployments
In addition to webhook-triggered trades, you can execute trades manually from the dashboard. These actions are available on both webhooks and copy groups.
| Action | Parameters | Description |
|---|---|---|
Place Order | Account(s), Symbol, Side, Quantity, Order Type, Price, TP/SL | Open a new position with full control over parameters |
Partial Exit | Account(s), Symbol, Exit % or Quantity | Close a percentage of an existing position |
Exit All | Account(s) | Close ALL open positions on selected accounts |
Exit by Symbol | Account(s), Symbol | Close all positions for a specific trading pair |
Cancel All Orders | Account(s) | Cancel all pending/open orders |
Update SL/TP | Account(s), Symbol, TP Price, SL Price | Set or update stop-loss and take-profit prices |
💡 Multiplier Application
The deployment multiplier scales the quantity for Place Order actions. It is not applied to a Partial Exit percentage — 50% closes half of each account's own position, whatever its multiplier — nor to Exit All / Close Symbol, which close the whole live position. A Partial Exit entered as an absolute quantity is still scaled, then capped at the live position. Full breakdown in Where the Multiplier Applies under Payload & Syntax.
Per account
SKIPPED occurs when: account is inactive, no position found for exit, or outside trading hours. COOLDOWN occurs when the same payload is sent within the cooldown window.
Per request (trigger logs)
SUCCESS: no account failed (also when no account is deployed). Skipped accounts do not count as failures.
PARTIAL: some accounts failed, or an entry went through but its take-profit levels could not all be placed.
FAILED: no account succeeded and at least one failed.
PENDING: still being processed.
HALTED: trading was halted platform-wide. The request is recorded and nothing was executed. Not a failure.
DUPLICATE: the request repeated an idempotency_key that was already used. It is recorded, nothing was placed, and it is not counted in the trigger totals.
Copy Groups allow you to execute trades simultaneously across multiple member accounts. The group owner can perform actions on all members' deployed accounts.
1. Create Group — Select broker, provide name and description
2. Deploy Your Accounts — Add your own accounts with multiplier
3. Share Access Code — Members join using the 8-character code
4. Execute Trades — All 6 manual actions available for member accounts
| Setting | Description |
|---|---|
multiplier | Per-member trade size multiplier (default 1.0). Scales absolute quantities and fixed-amount sizing only — percentage entries and percentage partial exits are per-member already and are left unscaled. |
is_active | Toggle member participation on/off |
qty_type: RISK_PCT is not available for copy groups. Copy-group orders are manual orders, and the terminal does not offer it; an order that asks for it anyway is refused with "RISK_PCT is not available for copy groups".
As a group owner, you can view:
• Positions — Live positions for all members
• Balances — Real-time balances for all members
• Open Orders — Pending orders for all members
• Execution Logs — Complete trade history with per-order details
You can restrict group membership by enabling invite-only mode. Only users whose client_id is in the invite list can join the group.
Discover and follow expert traders. Subscribe to their published webhooks and deploy your accounts to automatically copy their trades.
1. Browse the Marketplace or Copy Trading page to find strategies
2. Click Subscribe and enter the access code (if required)
3. Deploy your broker account to the subscribed webhook
4. All webhook triggers from the creator will execute on your account
Important
You cannot subscribe to your own webhooks. Your broker must match the webhook's broker type. Invite-only webhooks require your TradoConnectID to be in the invite list.
The marketplace lets you discover published strategies from other traders. As a creator, you can publish your webhooks and share your trading expertise.
1. Publish Your Webhook — Set welcome message, choose public or invite-only
2. Add Strategy Details — Tagline, description, tags, performance metrics
3. List on Marketplace — Make your strategy discoverable
4. Share Access Code — Share with your community
Published strategies can include detailed performance information:
The platform sends email notifications for important events to keep you informed about your account activity.
| Event | Email Subject |
|---|---|
Deploy | "Account Deployed — {Account} on {Webhook}" |
Undeploy | "Account Removed — {Account} from {Webhook}" |
Activate | "Deployment Activated — {Account} on {Webhook}" |
Deactivate | "Deployment Deactivated — {Account} on {Webhook}" |
Multiplier Change | "Multiplier Updated — {Account} on {Webhook}" |
Each email includes: webhook name, exchange, account name, client ID, multiplier value, status, access type, who performed the action, and timestamp.
Set specific hours during which your account should execute trades. Orders received outside the window are automatically skipped.
• Set start_time and end_time in 24-hour format (HH:MM)
• Trades outside this window are SKIPPED with reason "Outside trading hours"
• Set both to null for 24/7 trading (default)
• Configured per-account from the Add broker page
Time Zone
Trading time windows are evaluated in the server's timezone (UTC). Make sure to convert your local time to UTC when configuring.
Algo Lab
Write Python strategies, backtest on real CoinDCX Futures data, and run them live — all from your browser. Full API reference, indicator library, and examples.
Connect by Trado — Built by Windigotrade Pvt Ltd