Placing Orders From a Python Script via Webhook
A Python script can drive automated trading by POSTing a signal to the same webhook endpoint a TradingView alert would hit. The advantage over calling the broker API directly from the script is separation: your strategy logic stays a simple script that emits signals, while authentication, order construction, position sizing, risk limits, and reconciliation live in one hardened receiver. The script should send a signal, not an order — anything that decides how much to trade belongs behind the endpoint, not in front of it.
Python is where most quantitative research already lives. Getting a signal from a script into a live order is a smaller step than it looks, and the design decision that matters comes before any code.
Why not just call the broker API from the script
You can. For a single script trading a single account it is fewer moving parts, and fewer moving parts is a real argument.
It stops being the right answer as soon as you have more than one signal source. Every script then needs its own broker authentication, its own token refresh, its own order construction, its own sizing rules, and its own reconciliation logic. Those get copied, they drift, and the copy with the bug is the one running in production.
Routing through a webhook receiver inverts that. Scripts emit signals. One receiver owns everything dangerous: credentials, order construction, position sizing, daily limits, deduplication, and reconciliation against the broker. Add a second strategy and you write another small script rather than another integration.
It also means a Python script, a Pine Script alert, and anything else that can make an HTTP request all speak the same protocol to the same hardened component.
The pattern
The script's job is to decide that something happened and say so. In practice that is a POST with a JSON body containing a shared secret, a strategy identifier, an action, a symbol, and a unique signal identifier.
Three things to get right in the sending code.
Set a timeout on the request. A request with no timeout can hang indefinitely, and a strategy loop blocked on a hung HTTP call is a strategy that has silently stopped evaluating. Set one explicitly.
Handle the response, do not assume it. A 200 means the receiver accepted the signal. It does not mean an order filled, because the receiver acknowledges before placing to stay inside timeout budgets. Log the status and the body.
Do not retry blindly on failure. A retry after a timeout can produce a duplicate signal, which is why the idempotency key matters. Retry at most once, with the same key, and let the receiver deduplicate.
Keep the secret in an environment variable, not in the script. Scripts get shared, pasted into notebooks, and committed.
Generating the idempotency key
Build it from stable values so the same signal always produces the same key: strategy identifier, symbol, the bar or evaluation timestamp, and the action. A key built from the current wall-clock time defeats the purpose, because a retry generates a different key and the receiver sees two distinct signals.
This is the same discipline as generating keys in Pine Script rather than on receipt, for the same reason.
Where the script should stop
The most important boundary in this design.
A script should say what it observed. It should not decide how much capital to commit. Position sizing, maximum order size, maximum concurrent positions, and daily loss limits all belong in the receiver, enforced server-side on every signal regardless of source.
The reason is blunt: a payload is untrusted input. A bug that produces a quantity of 10000 instead of 100 is an ordinary bug in a research script and a catastrophic one if the receiver obeys it. Compute size from your own capital rules and treat any quantity in the payload as a suggestion to be bounded.
The divide-by-20 rule is the frame worth enforcing there: available trading capital divided by twenty as the ceiling on any single position. Put it behind the endpoint where a malformed payload cannot reach it — on a self-hosted deployment that limit lives in your own environment alongside the broker credentials, rather than in a vendor's configuration screen.
Scheduling and the market clock
A script that runs continuously needs to know when not to trade. Market holidays, early closes, and the difference between regular and extended hours are all sources of orders you did not intend.
Two defences, and use both. Gate in the script so it does not evaluate outside your intended window, and enforce a schedule in the receiver so a misbehaving script cannot trade at three in the morning. Controls that exist in only one place fail when that place has a bug.
If the script runs on a schedule rather than continuously, make sure a missed run is visible. A cron job that silently stopped looks exactly like a strategy with no signals.
Blocking, and why it matters more than it seems
If your script does its analysis and its HTTP call on the same thread, every request is time the strategy is not evaluating. On a slow response that gap can span the move you were trying to catch.
For a script emitting a handful of signals a day this is irrelevant. For anything evaluating frequently or across many symbols, keep the send off the evaluation path — a queue and a sender thread, or an async client. This is the same reason the receiving side keeps broker work off the request path using worker thread pools: the fast path stays fast because slow work happens somewhere else.
Testing
Point the script at a request inspector first and read what actually arrives. Confirm the body is valid JSON, the content type is what your receiver expects, and every field resolved rather than containing a Python object repr.
Then run against the receiver with orders disabled, so you can confirm routing, deduplication, and validation without anything reaching a broker. Then trade small and live.
Deliberately test the failure paths: send the same signal twice and confirm one order results; send a malformed body and confirm rejection; send with a wrong secret and confirm rejection; kill the receiver mid-run and confirm the script does not crash or spin.
The honest limits
Adding a receiver adds a hop. Two components can fail instead of one, and the network between them is a new dependency.
A 200 response is an acknowledgement, not a fill. If your script's logic depends on knowing a position exists, it needs to read that from the broker rather than infer it from a status code.
Signal delivery is not guaranteed. Design so that a lost signal is survivable — which in practice means exits that must happen should rest at the broker rather than depending on a future request arriving.
And none of this makes a strategy work. A Python script that emits bad signals produces bad orders faster and more reliably than a person would. Position sizing is the control that bounds loss, and unlike everything above it does not depend on a request succeeding.
Frequently asked questions
Can I place trades from a Python script? Yes, either by calling a broker API directly or by POSTing a signal to a webhook receiver that handles order placement. The second scales better across multiple strategies.
Why use a webhook instead of calling the broker directly? It centralises credentials, order construction, sizing, and risk limits in one component rather than duplicating them in every script.
Should the script decide position size? No. Size should be computed and bounded server-side, because a payload is untrusted input and a sizing bug in a script should not become a large order.
How do I avoid duplicate orders? Send a stable idempotency key built from strategy, symbol, timestamp, and action, and let the receiver deduplicate.
Does a 200 response mean my order filled? No. It means the signal was accepted. Receivers acknowledge before placing to stay within timeout budgets.
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. Stop orders do not guarantee an execution price and stop-limit orders may not execute at all. 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.