An unauthenticated webhook is an open door. Anyone who guesses or scrapes the URL can send fake signals to your trading server — and with automated execution wired up, that means fake trades hitting your broker in real time. If you're sending TradingView alerts to any execution layer, webhook security is not optional.
TradingView's alert system gives you two practical ways to authenticate: a shared secret in the URL, or an HMAC signature in the request body. This guide explains when each makes sense, how to implement them, and the mistakes that quietly break both.
The core constraint: no custom headers
Before anything else, understand the limitation. TradingView's alert configuration lets you set a URL and a Message body, and nothing else. You cannot add an Authorization header, a X-API-Key header, or any other custom HTTP header.
That eliminates the authentication pattern most web APIs use. What's left:
- Put the secret in the URL (as a path segment or query parameter)
- Put a signature inside the message body that the server can verify
Those are your two real options. Everything else — IP allowlisting, mutual TLS, geoblocking — is defense in depth layered on top, not a substitute.
Option 1: URL token authentication
The simplest approach. Generate a long random string, embed it in the webhook URL, and have your server reject requests that don't match.
https://your-server.com/webhook?token=a7f3c2e8b9d4f1a6c0e5b2d9f4a1c7e3b0d6f2a8c5e1b7d4f0a3c9e6b2d8f5a1
On the server side:
@app.route("/webhook", methods=["POST"])
def webhook():
token = request.args.get("token", "")
expected = os.getenv("WEBHOOK_TOKEN", "")
if not token or not hmac.compare_digest(token, expected):
return jsonify({"error": "unauthorized"}), 401
# ... process signal
What to get right:
- Length matters. Use at least 32 bytes of randomness.
python -c "import secrets; print(secrets.token_hex(32))"produces 64 hex characters — enough that brute force is infeasible. - Constant-time comparison. Use
hmac.compare_digest(), not==. String equality in most languages short-circuits on the first mismatched character, leaking information via timing. - Rotate on suspicion. If you ever paste the URL into a screenshot, a support chat, or a GitHub issue, rotate the token immediately. Assume anything that was in a screenshot is now public.
- Don't log the token. Most access-log defaults include the full URL including query strings. Either strip query params from your nginx logs or put the token in a path segment you can mask.
What URL tokens don't protect against:
- Replay attacks. Anyone who sniffs one valid request (or pulls it from a compromised log file) can replay it forever. Without a timestamp and nonce, the server can't tell yesterday's signal from today's.
- Log leakage. Intermediate proxies, CDN logs, load balancer logs, and browser histories all potentially capture the full URL. This is the #1 real-world way URL tokens leak.
URL tokens are good enough for most retail use cases if the token is long, rotated occasionally, and kept out of logs. They're not good enough for high-value accounts or multi-tenant systems.
Option 2: HMAC-SHA256 signatures
HMAC moves the secret out of the URL and into a cryptographic signature computed over the request body. The webhook URL is public; only someone who knows the shared secret can forge a valid signature.
The alert body looks like this:
{
"timestamp": 1713456000,
"action": "buy",
"symbol": "EURUSD",
"price": 1.0842,
"signature": "9f3c2e8b9d4f1a6c0e5b2d9f4a1c7e3b0d6f2a8c5e1b7d4f0a3c9e6b2d8f5a1b"
}
The server recomputes the signature over the body (minus the signature field) and compares:
import hmac, hashlib, time, json
def verify_signature(body: bytes, secret: str) -> bool:
try:
data = json.loads(body)
except ValueError:
return False
provided = data.pop("signature", "")
timestamp = int(data.get("timestamp", 0))
# Reject stale signatures (replay protection)
if abs(time.time() - timestamp) > 60:
return False
# Recompute HMAC over the canonical body
canonical = json.dumps(data, sort_keys=True, separators=(",", ":"))
expected = hmac.new(
secret.encode(), canonical.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(provided, expected)
What HMAC gives you that tokens don't:
- Replay protection. The timestamp + short window (60 seconds is standard) means a captured request is useless after a minute.
- Body integrity. An attacker can't modify the signal payload (change
symbolfromEURUSDtoBTCUSD, flipactionfrombuytosell, changemagic) without invalidating the signature. - Log-safe. The secret never appears in a URL, a query string, or an access log. Only the HMAC hash does, which is useless to replay.
What HMAC requires:
- TradingView Premium. Templating an HMAC signature into the alert body requires the
{{time}}substitution plus Premium features for computing the hash. Basic-tier TradingView alerts can't produce a valid signature. - Clock sync. Both TradingView and your server need accurate time. If your server's clock drifts more than the replay window, all signals fail. Run
chronyorntpd. - A bit more setup. You have to decide on a canonicalization format (how to serialize the body before hashing), handle JSON field ordering, and pick a replay window.
Which should you use?
Use URL tokens when: - You're on a free or basic TradingView plan (no HMAC templating) - Your trading capital is modest and the token is rotated occasionally - You control the full network path and can keep URLs out of logs
Use HMAC when: - You're on TradingView Premium or higher - You're running a multi-tenant service where leaked credentials from one user shouldn't affect others - You need an audit trail proving signals weren't tampered with en route - Your broker account is large enough that a single rogue trade could do real damage
For many retail setups, URL tokens with good hygiene are fine. For production trading infrastructure serving paying customers, HMAC is the correct default. If you're building a TradingView webhook to MT5 pipeline — see our pillar guide on how this architecture works — you should support both, because your users will span both tiers.
Common mistakes that break either scheme
Even with the right primitive, these mistakes sink webhook security:
- Accepting requests without
Content-Type: application/json. TradingView sometimes sendstext/plain. If your validator only runs on JSON requests, a misconfigured alert might bypass auth entirely. Validate regardless of content type. - Skipping signature verification in dev. A
DEV_MODE=trueflag that disables auth is fine for local testing, but make sure it's off in production. Many breaches come from debug flags committed to production configs. - Returning different error messages.
401 "Invalid token"vs401 "Token expired"tells an attacker exactly where they are in the auth flow. Return generic errors for all auth failures. - Ignoring the timestamp on HMAC. If you verify the signature but not the timestamp, you've built a signed-replay system. Always enforce a short window (30–60 seconds).
- Storing the secret in the database. If your database gets exposed, the secret goes with it. Store secrets in environment variables, a secrets manager (AWS Secrets Manager, Vault), or encrypted at rest with a key that lives elsewhere.
How iNakaTrader handles this
iNakaTrader supports all three authentication modes — HMAC-SHA256 with timestamp, URL token, and header token — selectable per license. New licenses default to per-user 64-character URL tokens so users on any TradingView plan can get started immediately. Upgrade to HMAC later without re-pasting every alert URL. The admin panel can rotate credentials in one click, and the old URL stops working instantly.
Ready to wire up authenticated webhook trading? Start with iNakaTrader →