TradingView sends alert webhooks as HTTP POST requests to a URL you choose, with the alert message as the body. Its docs are short and strict: "If a remote server takes longer than three seconds to process a request, the request will be cancelled" (How to configure webhook alerts).
TradingView resends in one case only: when your server answers with a status between 500 and 599, except 504. It waits 5 seconds and resends, up to 3 times, so at most 4 sends per trigger (Webhook resubmission). It doesn't resend timeouts.
A few other limits from the same docs are worth knowing before you debug anything else:
- Only ports 80 and 443 are accepted.
- IPv6 isn't supported, so your hostname needs an IPv4 (A) record.
- Webhook alerts only work with two-factor authentication enabled on the TradingView account.
- The request is sent as
application/jsonif the message is valid JSON, otherwise astext/plain.
What goes wrong in practice
If your endpoint does real work inside the request (calling a broker API, writing to a sheet, posting to Discord), you're racing that clock. People often report the same pattern: the alert log flags the webhook as failed, yet the destination got the data. The work happened, but the 200 came back too late. The Webhook status column in TradingView's alert log is where to look.
Restarts are subtler. If a reverse proxy answers 502 or 503 while your app restarts, TradingView will resend (three times, 5 s apart), and you may get the alert once the app is back. But if the connection is refused, the request times out, or the proxy answers 504, there's no resend and that alert is gone.
The pattern that fixes it
- Receive the POST and check a shared secret. TradingView's alert setup gives you a URL and a message, so put the secret in the message body (or in an unguessable URL path), not in a custom header.
- Write the payload to a durable queue and return 200 immediately.
- Have a worker deliver it to your bot, with its own retries and backoff.
- Make handling idempotent, because the same alert can arrive more than once.
- Park messages that keep failing in a dead-letter queue (DLQ), so you can inspect them instead of losing them.
- Reconcile TradingView's alert log against your order log daily, and keep protective stops resting at the broker.
Python (Flask sketch)
import json
from flask import Flask, request
app = Flask(__name__)
SECRET = "your-shared-secret"
def enqueue(raw: bytes) -> None:
"""Persist the raw alert (Redis list, SQLite/Postgres table...). Must be fast and durable."""
...
@app.post("/webhook")
def webhook():
raw = request.get_data()
try:
if json.loads(raw).get("secret") != SECRET:
return ("", 401)
except ValueError:
return ("", 400)
enqueue(raw) # persist first
return ("OK", 200) # ACK before any broker / Redis / Discord work
Node (Express sketch)
app.post("/webhook", express.raw({ type: "*/*" }), async (req, res) => {
let msg;
try { msg = JSON.parse(req.body.toString("utf8")); } catch { return res.sendStatus(400); }
if (msg.secret !== process.env.SECRET) return res.sendStatus(401);
await enqueue(req.body); // persist first (your durable queue)
res.status(200).send("OK"); // ACK before slow work
});
With a message like:
{"secret": "your-shared-secret", "id": "{{ticker}}-{{interval}}-{{time}}-{{strategy.order.action}}", "action": "{{strategy.order.action}}", "ticker": "{{ticker}}"}
The id gives you idempotency. Use the bar time {{time}}, not {{timenow}}, so the id is tied to the bar that fired and stays the same across resends. Two caveats: {{strategy.order.action}} only exists in strategy alerts, and messages built with Pine's alert() function don't substitute placeholders at all (Pine FAQ: alerts). For those, build the same string in Pine with str.format() from syminfo.ticker, timeframe.period and time.
What we built
We build HookWatch, which runs this ACK, queue, retry and DLQ layer as a hosted service for TradingView webhooks. Each alert is queued and answered with a 200 well inside TradingView's window. If your bot doesn't accept it, we retry delivery 5 times (after 2, 5, 15, 30 and 60 seconds, about 2 minutes in total). Alerts that still fail are parked in a dead-letter queue instead of being lost, and support can resend them. There's also an optional heartbeat that emails you if TradingView stops sending. It isn't a signal service and isn't a broker, just delivery. Keep your bridge; we handle the delivery hop to it. You still want the idempotency key above, because any retry layer, ours included, can deliver an alert more than once.
Starter is €19/month or €190/year (up to 3 destinations); Ops is €49/month or €490/year (up to 15). There's a free trial: 300 alerts or 7 days, whichever comes first, no card.
If you've built your own receiver for TradingView alerts, we'd like to hear how you handle retries and duplicates, and what you'd need to see before trusting a hosted hop in front of your bot.
Disclosure: We build HookWatch, the service described above. This post was drafted with AI assistance and reviewed and edited by the HookWatch team.
Top comments (0)