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.
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.
