Notas de quickstart
Publica alertas de caída en Discord desde tu listener de Python
El listener de caídas ya analiza cada alerta. Añade un POST de webhook y un cooldown por partido, y tu canal muestra cada movimiento.
El listener de caídas del quickstart de Python ya hace la parte difícil: una solicitud abierta, una línea de datos por lote, cada alerta analizada con su deporte, partido, selección, cuota vieja, cuota nueva y cuota justa. Convertirlo en un canal de Discord lleva una función más: formatear la alerta, enviarla por POST a un webhook y no dejar que el POST frene el stream.
Lo que necesitas antes del código
Dos cosas además del listener. Primero, un plan que empuja caídas: la página de precios muestra que las caídas empiezan con el plan push de 99 USD al mes y vienen incluidas en los planes de 149 y 229 USD, verificado el 2026-10-24. Sin uno, el stream responde con un mensaje de control plan_lacks_sse en lugar de alertas. Segundo, una URL de webhook de Discord, que está en los ajustes del canal, en Integraciones. Guárdala en una variable de entorno como tu clave de API.
El publicador
El cambio en el listener es pequeño. Donde el quickstart imprime cada alerta, llama a una función de notificación:
import json, os, time, requests
BASE = os.environ["ODDS_BASE_URL"]
KEY = os.environ["ODDS_API_KEY"]
HOOK = os.environ["DISCORD_WEBHOOK_URL"]
s = requests.Session()
s.headers["x-portal-apikey"] = KEY
last_sent = {}
def notify(a):
key = (a["home"], a["outcome"])
if time.time() - last_sent.get(key, 0) < 300:
return # mismo partido y selección en 5 minutos
last_sent[key] = time.time()
pct = (a["from_price"] - a["to_price"]) / a["from_price"] * 100
text = (f'{a["sport"]} | {a["home"]} | {a["outcome"]}: '
f'{a["from_price"]} -> {a["to_price"]} ({pct:.1f}%), '
f'justa {a["nvp"]}')
try:
requests.post(HOOK, json={"content": text}, timeout=10)
except requests.RequestException:
pass # el stream importa más que un mensaje
def listen():
r = s.get(BASE + "/odds-drop", params={"min_drop": 5},
stream=True, timeout=90)
r.raise_for_status()
for raw in r.iter_lines():
if not raw.startswith(b"data:"):
continue
msg = json.loads(raw[5:])
if isinstance(msg, dict):
continue # frame de control, no alertas
for a in msg:
notify(a)
El porcentaje es la misma aritmética que el feed mide: la caída dividida entre la cuota vieja. La cuota justa viene adjunta a la alerta, así que el mensaje lleva lo que el mercado cree que vale el lado, no solo a dónde se movió.
Mantén el canal legible
Un webhook que dispara con cada alerta se vuelve papel tapiz en un día. Dos guardas lo mantienen digno de lectura. Deja que el stream haga el filtro grueso: min_drop en la conexión fija el piso, 5 en el ejemplo documentado, y cualquier cosa debajo nunca llega a tu código. Luego deja que el cooldown haga el fino: cinco minutos por partido y selección, como arriba, para que una cuota que resbala en escalones produzca un mensaje, no seis.
Resiste las menciones y los embeds al principio. Las líneas de texto plano se leen más rápido, y un canal que solo suena cuando tú decides sigue siendo confiable.
Cuando Discord dice que no
Discord limita los webhooks por tasa y responde a las ráfagas con su propio 429. El publicador de arriba trata un envío fallido como un mensaje perdido, que es el defecto correcto para una alerta sobre la que no actuarías cinco minutos después. Si necesitas cada alerta entregada, ponlas en cola y vacía la cola despacio; no reintentes dentro del bucle de lectura, donde un webhook lento frena el stream.
El listener ya era toda la integración. El webhook es el print, mejorado.