Protect webhook with a secret path token.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Artemii Peretiachenko 2026-07-24 16:44:09 +02:00
parent 7fd6f38d87
commit b9a9ed1636
5 changed files with 21 additions and 7 deletions

View file

@ -1,5 +1,6 @@
TELEGRAM_BOT_TOKEN=123456:ABC-DEF
TELEGRAM_CHAT_ID=-1001234567890
TELEGRAM_MESSAGE_THREAD_ID=1
WEBHOOK_SECRET=change-me-to-a-long-random-string
HOST=0.0.0.0
PORT=8000

View file

@ -32,7 +32,7 @@ curl http://127.0.0.1:8000/health
4. Put HTTPS in front (nginx/Caddy) and point TradingView webhook to:
`https://your-domain/webhook`
`https://your-domain/h/<WEBHOOK_SECRET>`
Bot must be added to the group/forum and allowed to post in the target topic.
@ -92,7 +92,7 @@ Build the JSON inside `alert()`. A continuous one-line string is fine.
In the TradingView alert dialog:
- Webhook URL: `https://your-domain/webhook`
- Webhook URL: `https://your-domain/h/<WEBHOOK_SECRET>`
- Message: only `{{alert_message}}` (do not paste a second JSON next to it)
On **seq == 1**: store `entry_price = close`, freeze SL/TPs, and `signal_time = time / 1000` (bar open, unix seconds). On **seq > 1**: keep those frozen fields; only refresh `current_price` (= live `close`). Example shape:
@ -147,4 +147,4 @@ For seq >1, profit % is signed vs entry; RR is `|priceentry| / |entrySL|`
## Endpoints
- `GET /health``{"status":"ok"}`
- `POST /webhook` → signal payload above
- `POST /h/<WEBHOOK_SECRET>` → signal payload above (wrong/missing secret → `404`)

View file

@ -13,6 +13,7 @@ class Settings(BaseSettings):
telegram_bot_token: str
telegram_chat_id: str
telegram_message_thread_id: int
webhook_secret: str
host: str = "0.0.0.0"
port: int = 8000

View file

@ -2,6 +2,7 @@ from __future__ import annotations
import json
import logging
import secrets
from typing import Any
from fastapi import FastAPI, HTTPException, Request
@ -21,7 +22,13 @@ logging.basicConfig(
)
logger = logging.getLogger(__name__)
app = FastAPI(title="TV Signals → Telegram", version="1.0.0")
app = FastAPI(
title="TV Signals → Telegram",
version="1.0.0",
docs_url=None,
redoc_url=None,
openapi_url=None,
)
def _parse_signal_body(raw: bytes) -> SignalPayload:
@ -50,10 +57,13 @@ async def health() -> dict[str, str]:
return {"status": "ok"}
@app.post("/webhook")
async def webhook(request: Request) -> JSONResponse:
signal = _parse_signal_body(await request.body())
@app.post("/h/{token}")
async def webhook(token: str, request: Request) -> JSONResponse:
settings = get_settings()
if not secrets.compare_digest(token, settings.webhook_secret):
raise HTTPException(status_code=404, detail="Not Found")
signal = _parse_signal_body(await request.body())
caption = format_caption(signal)
logger.info(
"Signal received: %s %s seq=%s tf=%s",

View file

@ -14,6 +14,8 @@ services:
sync: false
- key: TELEGRAM_MESSAGE_THREAD_ID
sync: false
- key: WEBHOOK_SECRET
sync: false
- key: MPLBACKEND
value: Agg
- key: HOST