#!/usr/bin/env python3 """ utils_webhook.py — Webhook dispatch utility (fire-and-forget, HMAC-SHA256) HRT-80 """ import hashlib import hmac import json import logging import requests from api_tokens_db import get_db logger = logging.getLogger("turf_saas.webhook") EVENT_NEW_PREDICTION = "new_prediction" EVENT_VALUE_BET = "value_bet" def dispatch_webhook(user_id: str, event_type: str, payload: dict) -> None: """ Send HMAC-signed webhook POST to URL configured by user. Fire-and-forget: errors logged, never re-raised. Timeout: 5s. """ conn = get_db() try: row = conn.execute( "SELECT url, secret FROM user_webhooks WHERE user_id = ?", (str(user_id),), ).fetchone() except Exception as e: logger.warning("dispatch_webhook: DB error for user %s: %s", user_id, e) return finally: conn.close() if not row: return url = row["url"] secret = row["secret"] body = json.dumps( {"event": event_type, "data": payload}, ensure_ascii=False, separators=(",", ":"), ) signature = hmac.new( secret.encode("utf-8"), body.encode("utf-8"), hashlib.sha256 ).hexdigest() headers = { "Content-Type": "application/json", "X-Turf-Signature": f"sha256={signature}", "X-Turf-Event": event_type, "User-Agent": "TurfSaaS-Webhook/1.0", } try: resp = requests.post(url, data=body, headers=headers, timeout=5) logger.info( "Webhook dispatched to user %s (event=%s, status=%s)", user_id, event_type, resp.status_code, ) except requests.exceptions.Timeout: logger.warning( "Webhook timeout for user %s (event=%s, url=%s)", user_id, event_type, url ) except requests.exceptions.RequestException as e: logger.warning( "Webhook failed for user %s (event=%s): %s", user_id, event_type, e ) except Exception as e: logger.warning( "Webhook unexpected error for user %s (event=%s): %s", user_id, event_type, e, )