Lesson 5 — Webhooks & alerts

Skip to content

Lesson 5 — Webhooks & alerts

Receive TradingView alerts via a secure webhook, verify the HMAC signature, map the alert to a Betfair market/selection and trigger an idempotent order placement in the sandbox.

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, auth, market lookup, order placement)
  • Python Flask or similar web framework basic knowledge
  • A public HTTPS endpoint (ngrok for local testing) and a shared webhook secret

Overview

We’ll implement a small Flask webhook that verifies an HMAC signature, parses a TradingView JSON alert, maps the alert to a market & selection, constructs an idempotent order (customerRef) and calls the place_order() function from Lesson 4.

1) TradingView Pine alert JSON template

Use this JSON message as the alert body in TradingView (Pine) — it’s a recommended structure that includes symbol mapping and a client idempotency token.

{
  "symbol": "TEAM1/TEAM2",
  "strategy": "sma-cross",
  "signal": "BUY",
  "side": "BACK",
  "price": 2.5,
  "stake": 1.0,
  "client_ref": "{{timenow}}-{{strategy.order_id}}"
}

Note: TradingView allows templated fields. Replace {{timenow}} and other variables with values supported by Pine scripts. The webhook must be HTTPS and secured by a secret/HMAC.

2) Flask webhook receiver (webhook.py)

This example verifies an HMAC-SHA256 signature sent in the X-Signature header and responds with 200/400 accordingly. Use ngrok to test locally (ngrok http 5000).

from flask import Flask, request, jsonify, abort
import hmac, hashlib, os, json
from orders import place_order  # from Lesson 4
from market_lookup import find_market_selection  # your helper that maps symbols to marketId/selectionId

app = Flask(__name__)
WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET', 'replace_with_secure_secret')

def verify_hmac(raw_body, signature):
    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()  # raw bytes
    sig = request.headers.get('X-Signature')  # tradingview/custom header
    if not verify_hmac(raw, sig):
        abort(400, 'Invalid signature')

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

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

    # Map symbol to market and selection
    market_id, selection_id = find_market_selection(payload['symbol'])
    if not market_id or not selection_id:
        return jsonify({'success': False, 'error': 'market not found'}), 404

    # Place idempotent order (place_order handles retries and idempotency via client_ref)
    try:
        resp = place_order(
            market_id=market_id,
            selection_id=selection_id,
            side=payload['side'],
            stake=payload['stake'],
            price=payload['price'],
            idempotency_token=payload.get('client_ref')
        )
    except Exception as e:
        # Log and return 500 so sender may retry (TradingView will not retry automatically; your broker may)
        return jsonify({'success': False, 'error': str(e)}), 500

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

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

Testing locally: run ngrok (or similar) and set TradingView alert webhook to the public URL. Send a test alert with the same HMAC signing (use shared secret to compute signature).

3) HMAC signature generation (Python example)

Compute the signature on the TradingView side in Pine or in your alert-sending intermediary. Here is how you compute HMAC-SHA256 in Python (for testing):

import hmac, hashlib

def sign_payload(secret: str, body_bytes: bytes) -> str:
    return hmac.new(secret.encode(), body_bytes, hashlib.sha256).hexdigest()

# Example
secret = 'replace_with_secret'
body = b'{"symbol":"TEAM1/TEAM2","side":"BACK","price":2.5,"stake":1.0,"client_ref":"123"}'
sig = sign_payload(secret, body)
print('X-Signature:', sig)

Security: Use HTTPS always. Do not accept unsigned requests. Use constant-time compare (hmac.compare_digest) to avoid timing attacks.

4) Mapping TradingView symbols to Betfair market & selection

Implement a mapping table or fuzzy matcher. Example fallback: use event name + market type heuristics and then call market lookup to confirm the match.

# market_lookup.py (simplified)
from auth import request

def find_market_selection(symbol):
    # Symbol example: "TEAM1/TEAM2"
    # Map to event search terms
    teams = symbol.split('/')
    search = {'filter': {'textQuery': ' '.join(teams), 'marketTypeCodes': ['MATCH_ODDS']}, 'maxResults': 5}
    cat = request('GET', '/marketCatalogue', params=search)
    # Heuristic: pick first with both team names in event or market name
    for m in cat:
        name = (m.get('event',{}).get('name','') + ' ' + m.get('marketName','')).lower()
        if all(t.lower() in name for t in teams):
            # extract selection id for the desired runner (e.g., TEAM1 or TEAM2)
            for r in m.get('runners', []):
                if r.get('runnerName','').lower().startswith(teams[0].lower()):
                    return m.get('marketId') or m.get('marketId') or m.get('market_id'), r.get('selectionId') or r.get('id')
    return None, None

Important: Use conservative matching to avoid placing orders on wrong markets. Log candidate matches and require live verification before auto-trading in production.

5) Reliability & safety considerations

  • Make webhook handlers idempotent — use client_ref to avoid duplicate processing for retries.
  • Rate-limit or queue incoming alerts to avoid bursts; use a worker queue (Redis/RQ, Celery) to decouple HTTP response from order placement.
  • Log raw payloads, signature headers, matched market ids and order responses for audit and debugging.
  • Use monitoring/alerts for webhook failures and error spikes.
  • Start with small stakes in sandbox then move to cautious live deployment with manual approvals.

What you’ll build next

Lesson 6 will cover backtesting & simulation in Jupyter, letting you replay signals and evaluate strategy performance before committing live capital.

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 *