Files
turf_saas/api_v1/utils_webhook.py
DevOps Engineer f300e44c74 feat(HRT-80): API Token personnel + Webhook alertes (Pro)
- Nouveaux fichiers: api_tokens_db.py, api_v1/routes/user_tokens.py, api_v1/utils_webhook.py
- Migration DB idempotente: tables user_api_tokens + user_webhooks
- Endpoints POST/DELETE /api/v1/user/api-token (Pro only)
- Endpoints POST/DELETE /api/v1/user/webhook (Pro only, HTTPS requis)
- HMAC-SHA256 fire-and-forget dispatch webhook
- auth.py: validate_api_key() + X-API-Key fallback dans jwt_required_middleware
- saas_auth.py: import logging au niveau module, validate_api_key(), X-API-Key fallback
- api_v1/__init__.py: enregistrement user_tokens_bp
- 24 tests pytest — tous passent

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-29 17:25:30 +02:00

81 lines
2.1 KiB
Python

#!/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,
)