Idempotency in Trade Webhooks

By Stax Team

Idempotency means processing the same signal twice produces the same result as processing it once. It matters for trade webhooks because TradingView sends one unacknowledged POST per alert, so a duplicate is indistinguishable from a genuine repeat — and the cost of getting it wrong is a doubled position rather than a duplicated database row. The mechanism is a unique identifier per signal, recorded durably before the order is placed, checked before any order is submitted.

Most webhook guides mention idempotency in a bullet and move on. In trading it deserves more, because this is the one class of bug where the failure costs money immediately and silently.

Where duplicates come from

Four sources, and only one is malicious.

Replay. TradingView does not sign requests, so a captured payload can be resent verbatim and will validate against a shared secret. Nothing in it expires.

Alert misconfiguration. A frequency of once per bar rather than once per bar close can fire repeatedly as a condition flickers intrabar. Two alerts on the same script, or the same script on two charts, produce genuine duplicates from your own setup.

Your own retries. If the receiver queues work and the queue retries on failure, an order that actually succeeded but timed out on the response gets retried.

Restarts. A receiver that crashes mid-processing and replays its queue on startup reprocesses whatever was in flight.

Notice that three of the four are self-inflicted. Idempotency is not primarily an attack defence; it is protection against your own infrastructure behaving normally.

The mechanism

Generate a unique identifier per signal, in the script. Not on receipt — a receiver that generates its own identifier gives two copies of the same signal two different identifiers, which defeats the purpose entirely. Build it in Pine Script from something stable: strategy identifier, symbol, timeframe, bar time, and order action. The same signal produces the same key; a genuinely new signal produces a new one.

Record it durably before placing the order. This ordering is the whole design. Check the store, insert the key, then submit. If you submit first and record after, a crash between the two leaves you having placed an order with no record — and the retry places a second one.

Make the check and insert atomic. Two requests arriving simultaneously can both check, both find nothing, and both proceed. Use a unique constraint in the database and let the insert fail for the loser, or a single atomic operation in whatever store you use. A check followed by a separate insert is a race condition, and under a burst of alerts it will be hit.

Return success on a duplicate. If the key already exists, respond 200 without placing anything. An error response invites more retries.

Store the outcome, not just the key. Recording what happened lets a duplicate return the original result rather than an ambiguous acknowledgement, and gives you an audit trail when reconciling.

Choosing the key

The key defines what counts as the same signal, so its composition is a design decision.

Too narrow and legitimate signals collide. A key of symbol and action alone means two genuine entries on the same instrument on the same day look identical, and the second is silently dropped — a missed trade rather than a duplicate, which is arguably worse because it is invisible.

Too wide and duplicates slip through. Including a timestamp that differs between the original and the replay defeats deduplication entirely.

Bar time is usually the right anchor, because it is stable across replays of the same signal and different for the next one. Combine it with strategy, symbol, timeframe, and action.

Set a retention window rather than storing keys forever, but make it long enough to cover the longest realistic replay gap.

The three-second constraint shapes this

TradingView cancels a request that takes longer than three seconds, so the handler cannot place an order synchronously and still respond in time. The standard structure is: validate the secret, check and insert the idempotency key, enqueue the work, return 200. Order placement happens behind the response.

The important consequence is that the key must be recorded in the synchronous path, not in the worker. If deduplication happens after the queue, two duplicates both enqueue and both get processed. Deduplicate at the door.

This is also where processing belongs on a separate execution path from request handling — moving broker calls onto worker thread pools or a queue keeps the acknowledgement fast while the actual work proceeds without competing with inbound requests.

What idempotency does not solve

The genuinely hard case: your receiver submits an order and the connection drops before a response arrives. You do not know whether it was placed.

An idempotency key on the inbound webhook does not help, because the duplicate risk here is on the outbound side. The correct recovery is to query the broker rather than resubmit — ask what orders exist before taking any further action. Resubmitting on ambiguity is how a receiver doubles a position.

Some broker APIs accept a client-supplied order identifier, which turns reconciliation into a lookup rather than a guess based on timestamps and symbols. Where that is available, use it.

Idempotency also does not reconcile state drift. If your receiver believes it holds a position it does not, every subsequent signal is evaluated against a fiction. Reconcile against the broker periodically and on every restart, and treat the broker as authoritative — because it is.

The honest limits

Deduplication is bounded by retention. A replay outside the window is a new signal.

It cannot distinguish a malicious replay from a legitimate repeat, because they are byte-identical. It prevents both, which occasionally means suppressing something you wanted.

A missed duplicate costs a doubled position. A false positive costs a missed trade. Both are real and the key design decides which way you err.

And idempotency is a correctness control, not a risk control. It ensures you place the order you meant to place once. It has nothing to say about whether that order was a good idea. Position sizing is what bounds loss — capital divided by twenty as the ceiling per position, under the divide-by-20 rule — and on a self-hosted deployment those limits live in your environment where a hostile payload cannot reach them.

Frequently asked questions

What is idempotency in a trade webhook? Processing the same signal twice produces the same result as processing it once — one order, not two.

How do I generate an idempotency key? In the sending script, from stable values: strategy, symbol, timeframe, bar time, and action. Generating it on receipt defeats the purpose.

Where should the key be checked? In the synchronous request path, before enqueueing work. Deduplicating after the queue means duplicates are already in it.

What if my order times out after submission? Query the broker to establish what exists rather than resubmitting. An inbound idempotency key does not cover outbound ambiguity.

How long should keys be retained? Long enough to cover the longest realistic replay gap. A replay outside the window is treated as a new signal.


Disclaimer: This article is educational content about software engineering and trading automation. It is not investment advice, financial advice, tax advice, legal advice, or a recommendation to buy or sell any security. Any instruments named are used solely to illustrate mechanics. Options trading involves substantial risk of loss and is not suitable for all investors. Please read Characteristics and Risks of Standardized Options before trading options. Automated trading systems carry additional risks including software defects, missed or duplicated signals, network and connectivity failures, third-party service changes and outages, and unintended order behaviour that may prevent orders from being placed, modified, or cancelled. Past performance does not indicate future results, and no configuration, alert setup, position-sizing rule, or risk setting can guarantee a profit or prevent a loss.

StaxInvesting LLC sells self-hosted trading software. It is not a broker-dealer, investment adviser, or financial institution, and it does not manage accounts, hold member funds, place trades on behalf of members, or access member brokerage accounts. Members run the software in their own cloud environment, connect their own brokerage accounts under their own credentials, and are solely responsible for their configuration, their credential security, their compliance with third-party terms of service, and every trade executed in their account. Third-party platform details described here reflect publicly available documentation as of publication and are subject to change without notice; always verify against current official documentation. Consult a qualified financial adviser and tax professional regarding your individual circumstances.