Lesson 5 — Webhooks & Pine alerts

Skip to content

Lesson 5 — Webhooks & Pine alerts

Build a secure webhook receiver for TradingView Pine alerts, validate HMAC signatures, map alert symbols to exchange testnet markets, and forward idempotent order instructions to the trading layer.

Estimated time: 45–60 minutes • Skill level: Beginner → Intermediate

Use this as a starting point. Don’t commit secrets.

Prerequisites

  • Lessons 1–4 completed (env, HMAC auth, market data, order placement)
  • A public HTTPS webhook endpoint (ngrok for local testing)
  • TradingView plan that supports webhooks (alerts with webhook URL)

1) TradingView Pine alert JSON template

Use a structured JSON body from your Pine script so the webhook can parse it reliably. Include a client idempotency token (client_ref) to avoid duplicate trades.

{
  "symbol": "BTCUSDT",            // use mapping compatible with your exchange lookup layer
  "exchange": "binance",
  "strategy": "rsi-break",
  "signal": "BUY",
  "side": "BUY",
  "price": 42000.0,
  "size": 0.001,
  "client_ref": "tv_{{timenow}}_{{strategy.order_id}}"
}

Tip: In Pine use alert() with a JSON string then set TradingView webhook URL. Keep payload small and predictable. Use a template for client_ref that is unique per alert.

2) Flask webhook receiver (webhook.py)

This Flask example verifies an HMAC-SHA256 signature in the X-Signature header, validates payload fields, enqueues the order request, and returns a 200 → enqueue, don’t block. Replace mapping/queue with your infra (Redis, RabbitMQ, SQS).

from flask import Flask, request, jsonify, abort
import hmac, hashlib, os, json
from dotenv import load_dotenv
from queue import SimpleQueue
from mapping import map_symbol_to_market  # implement in your codebase
from orders import enqueue_order         # small wrapper to push to worker queue

load_dotenv()
WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET', 'replace-me')

app = Flask(__name__)
work_queue = SimpleQueue()  # replace with Redis/Celery in production

def verify_hmac(raw_body: bytes, signature: str) -> bool:
    if not signature:
        return False
    mac = hmac.new(WEBHOOK_SECRET.encode('utf-8'), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    raw = request.get_data()
    sig = request.headers.get('X-Signature') or request.headers.get('X-Signature-256')
    if not verify_hmac(raw, sig):
        abort(400, 'Invalid signature')

    try:
        payload = request.get_json(force=True)
    except Exception:
        abort(400, 'Invalid JSON')

    required = ['symbol','side','client_ref','size']
    if not all(k in payload for k in required):
        return jsonify({'success': False, 'error': 'missing fields'}), 400

    # Map TradingView symbol to exchange market
    market_info = map_symbol_to_market(payload.get('symbol'), payload.get('exchange'))
    if not market_info:
        return jsonify({'success': False, 'error': 'market not found'}), 404

    # Build idempotent order instruction
    order_instruction = {
      'client_ref': payload['client_ref'],
      'exchange': payload.get('exchange'),
      'symbol': market_info['symbol'],
      'side': payload['side'],
      'price': payload.get('price'),
      'size': payload['size'],
      'meta': {'raw_payload': payload}
    }

    # Enqueue for worker to place order (fast response to TradingView)
    try:
        enqueue_order(order_instruction)  # push to queue (Redis/Celery/RQ) - implement this
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500

    return jsonify({'success': True}), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Important: Do not place orders synchronously in the HTTP request handler. Enqueue and let a worker process orders to avoid timeouts and to retry safely.

3) Mapping TradingView symbol → exchange market

Implement a conservative mapping layer (mapping.py) that resolves TradingView symbols to exchange-specific symbols or market IDs. Prefer a manual mapping table for high-risk markets.

# mapping.py (simple example)
# maintain a small dictionary for critical symbols
SYMBOL_MAP = {
  ('BTCUSDT','binance'): {'symbol': 'BTC/USDT'},
  ('ETHUSDT','binance'): {'symbol': 'ETH/USDT'}
}

def map_symbol_to_market(tv_symbol, exchange='binance'):
    # try exact lookup then fallback to a lightweight search via exchange API
    key = (tv_symbol.upper(), exchange.lower())
    if key in SYMBOL_MAP:
        return SYMBOL_MAP[key]
    # fallback: call exchange REST search (carefully)
    # from auth_hmac import public_get
    # results = public_get('/markets', params={'q': tv_symbol})
    # pick nearest result after heuristics
    return None

Safety: prefer human-reviewed mapping for real-money trading. If automapping, add a confirmation step in staging and extensive logging.

4) Worker: validate idempotency & place order (worker.py)

Worker consumes queue messages and calls the order placement function. It must check idempotency (client_ref) and persist outcomes.

from orders import place_order_idempotent, log_order, find_order_by_client_ref

def worker_process(instruction):
    # instruction: dict built in webhook
    # 1) check idempotency
    existing = find_order_by_client_ref(instruction['client_ref'])
    if existing:
        # already processed, skip or update status
        return existing

    # 2) pre-trade checks (size limits, daily PnL, hours)
    # run pre_trade_checks(...) — implement as needed

    # 3) place order via orders layer (handles retries)
    resp = place_order_idempotent(
        exchange=instruction['exchange'],
        symbol=instruction['symbol'],
        side=instruction['side'],
        size=instruction['size'],
        price=instruction.get('price'),
        client_ref=instruction['client_ref']
    )

    # 4) persist result
    log_order({'client_ref': instruction['client_ref'], 'response': resp})
    return resp

Idempotency pattern: store client_ref before performing the external call or use transactional DB operations so duplicates are prevented under retries.

5) Pine → Webhook security & best practices

  • TradingView cannot sign requests with HMAC. Use a shared secret: compute HMAC on the server side only if you control the alert sender. Alternatively, place the secret inside a lightweight relay you control that signs requests onward.
  • Use a relay service or small script that receives TradingView alerts and attaches an HMAC using your secret before forwarding to your production webhook. This prevents exposing the secret in TradingView.
  • Require client_ref and reject duplicate processing if client_ref already used.
  • Rate-limit inbound alerts and queue them; reject or throttle bursts.
# relay example (pseudo)
# TradingView POST -> Relay (no secret) -> Relay computes HMAC using WEBHOOK_SECRET and forwards to your /webhook
# Relay should run where you control the environment and secret. Do NOT store your production signing secret in TradingView.

6) Testing and local debugging

  • Use ngrok to provide a public HTTPS URL for local webhook testing: ngrok http 5000
  • Manually compute HMAC for test requests and send with curl or Postman to simulate TradingView
  • Start with sandbox testnets and tiny sizes for end-to-end validation
# example curl test (compute sig in Python, then curl)
curl -X POST https://your-ngrok-url/webhook -H "Content-Type: application/json" -H "X-Signature: {sig}" --data '{"symbol":"BTCUSDT","side":"BUY","size":0.001,"client_ref":"test-1"}'

7) Reliability & operational patterns

  • Decouple webhook ingestion from execution via durable queue to allow retries and visibility.
  • Implement worker retries with exponential backoff and idempotency tokens to avoid duplicate orders.
  • Log raw alerts, mapping decisions, order requests and order responses for auditing and dispute resolution.
  • Monitor webhook error rates and signature failures to detect suspicious traffic or misconfiguration.

What you’ll build next

Lesson 6 will cover backtesting & simulation so you can replay TradingView signals against historical market data to measure strategy performance before using real money.

Back to course hub

← Previous
Author: Stephane Patteux • Part of the Build your own bot series

Don’t miss our blog post!

We don’t spam! Read our privacy policy for more info.

Leave a Reply

Your email address will not be published. Required fields are marked *