Integración de API y Webhooks: cómo conectar Predite con tu propio stack
La mayor parte de Predite vive en el dashboard, pero los casos límite más interesantes ocurren cuando dejas de hacer clic y empiezas a escribir scripts. Quizás quieras que las señales de EV se canalicen a un canal de Discord en el instante en que el escáner las encuentra. Quizás corras un bot fuera de la plataforma y necesites tus posiciones en vivo de Polymarket como JSON cada minuto. Quizás quieras que un disparador de stop-loss te alerte, publique en Slack y arranque un flujo de trabajo de n8n que cierre la posición automáticamente. Para eso existen la API y el sistema de webhooks de Predite.
Esta guía recorre toda la superficie de integración de principio a fin: generar una API key, llamar a los endpoints REST con autenticación Bearer, respetar los límites de tasa, suscribirse a eventos de webhook, verificar las firmas de entrega con HMAC-SHA256 y, finalmente, ensamblar todo en un pipeline de alertas personalizado. Todo lo que está aquí es exclusivo del plan Bot ($99/mo): los planes Starter ($29) y Pro ($59) usan el dashboard y las notificaciones integradas, pero el acceso programático (API keys y webhooks salientes) está restringido a Bot.
Generar una API key
Predite usa API keys de larga duración para la API REST. Están limitadas al alcance de tu cuenta, así que cualquier key puede leer tu portafolio y el feed de señales, y nada más: no hay endpoints de escritura, por lo que una key filtrada no puede realizar operaciones ni mover fondos.
- Abre Configuración → API en el dashboard. Si no estás en el plan Bot, verás un aviso de actualización en lugar del gestor de keys.
- Haz clic en Crear API key y dale una etiqueta descriptiva, como
n8n-prododiscord-bot. La etiqueta es solo para tu propio control cuando tienes varias keys. - Predite genera la key y te la muestra una sola vez. Cópiala de inmediato y guárdala en un gestor de secretos o en tu entorno.
Una key se ve así:
``
pdt_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
``
El formato es el prefijo pdt_live_ seguido de 32 caracteres hexadecimales: 40 caracteres en total. En el servidor solo se almacena un hash SHA-256 de la key completa, más los primeros 16 caracteres (pdt_live_ + 7 caracteres) como prefijo no secreto, de modo que el dashboard pueda mostrarte pdt_live_a1b2c3… en la lista de keys sin almacenar nunca el secreto en texto plano. Esa es también la razón por la que no podemos volver a mostrarte la key completa después de crearla: literalmente no la tenemos.
Algunas notas operativas:
- •Revocar una key es un borrado lógico (soft delete). La fila se conserva para auditoría, el estado cambia a
revokedy cualquier solicitud que la use empieza a devolver 401 de inmediato. Rota creando una key nueva, desplegándola y luego revocando la anterior. - •El dashboard registra
last_used_aty un contador de solicitudes por key, para que puedas detectar una key que dejó de usarse (señal de que tu integración se rompió) o una más activa de lo esperado (señal de que se filtró). - •Trata la key como una contraseña. Nunca la subas al control de versiones, nunca la pongas en JavaScript del lado del cliente, nunca la pegues en una captura de pantalla. Todos los ejemplos de abajo la leen desde una variable de entorno por ese motivo.
Autenticar solicitudes
Cada llamada a la API REST lleva la key en el encabezado estándar Authorization como un token Bearer:
``
Authorization: Bearer pdt_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
``
El servidor extrae el token, lo hashea, busca la key activa que coincide y compara los hashes. Si el encabezado falta, está malformado, apunta a una key revocada o la key expiró, obtienes:
``json
{ "error": "invalid_api_key" }
``
con estado HTTP 401. No hay un paso de inicio de sesión separado ni renovación de token: la key es la credencial. Configúrala una vez en tu cliente y listo.
Los endpoints REST
La API v1 es deliberadamente pequeña y de solo lectura. Dos endpoints cubren los datos que la mayoría de las integraciones realmente necesitan: tu portafolio y el feed de señales. Ambos viven bajo /api/v1/ y ambos devuelven JSON con una marca de tiempo ISO en generated_at, para que puedas saber qué tan reciente es una respuesta.
GET /api/v1/portfolio
Devuelve tus posiciones en vivo y un resumen del portafolio, leído desde la wallet de Polymarket que conectaste en Configuración → Plataformas.
``bash
curl -s https://app.predite.io/api/v1/portfolio \
-H "Authorization: Bearer $PREDITE_API_KEY"
``
Una respuesta típica:
``json
{
"wallet": "0x3a1f...8c2d",
"positions": [
{
"market": "Will the Fed cut rates in July 2026?",
"outcome": "Yes",
"shares": 420.0,
"avg_price": 0.61,
"current_price": 0.68,
"value": 285.6,
"unrealized_pnl": 29.4
}
],
"summary": {
"total_value": 4128.55,
"unrealized_pnl": 312.10,
"open_positions": 7
},
"generated_at": "2026-06-01T14:32:08.114Z"
}
``
Si todavía no conectaste una wallet, obtendrás { "positions": [], "summary": null, "error": "no_wallet_connected" } con estado 200, así que revisa el campo error en lugar de asumir que un 200 significa que hay datos. El endpoint lee los mismos datos de posiciones on-chain que usa el dashboard, así que el P&L aquí coincide con lo que ves en la interfaz.
GET /api/v1/signals
Devuelve las principales señales de EV (expected value) del escáner de mercados: el mismo motor que alimenta el feed de señales del dashboard. Cada señal compara el precio actual del mercado con el precio justo estimado por la AI de Predite e informa el edge en puntos porcentuales.
Acepta dos parámetros de consulta:
- •
limit— cuántas señales devolver, de 1 a 100, por defecto 20. - •
min_edge— edge mínimo en puntos porcentuales, por defecto 5. Úsalo para filtrar señales marginales.
``bash
curl -s "https://app.predite.io/api/v1/signals?limit=10&min_edge=8" \
-H "Authorization: Bearer $PREDITE_API_KEY"
``
``json
{
"signals": [
{
"market_title": "Will Bitcoin close above $120k on 2026-06-30?",
"direction": "YES",
"edge_pp": 11.4,
"market_price": 0.42,
"ai_price": 0.534,
"created_at": "2026-06-01T14:05:00.000Z"
}
],
"count": 1,
"filters": { "min_edge": 8, "limit": 10 },
"generated_at": "2026-06-01T14:32:40.781Z"
}
``
Aquí un edge_pp de 11.4 significa que el precio justo de la AI (0.534) está 11.4 puntos por encima del precio de mercado (0.42) en el lado YES: el tipo de brecha que busca un trader de EV. Los resultados se ordenan por edge de forma descendente y luego por recencia. El bloque filters te devuelve lo que se aplicó, para que puedas confirmar que tus parámetros se interpretaron correctamente.
Un cliente en Python
Aquí tienes un pequeño wrapper que maneja la autenticación y ambos endpoints. Usa requests, pero la estructura se traslada a cualquier cliente HTTP.
```python import os import requests
BASE = "https://app.predite.io/api/v1" KEY = os.environ["PREDITE_API_KEY"] SESSION = requests.Session() SESSION.headers["Authorization"] = f"Bearer {KEY}"
def get_portfolio(): r = SESSION.get(f"{BASE}/portfolio", timeout=10) r.raise_for_status() return r.json()
def get_signals(limit=20, min_edge=5.0): params = {"limit": limit, "min_edge": min_edge} r = SESSION.get(f"{BASE}/signals", params=params, timeout=10) r.raise_for_status() return r.json()
if __name__ == "__main__": for s in get_signals(limit=5, min_edge=10)["signals"]: print(f"{s['edge_pp']:+.1f}pp {s['direction']:>3} {s['market_title']}") ```
Reutilizar una sola Session mantiene viva la conexión entre llamadas, lo cual importa una vez que haces polling de forma programada.
Límites de tasa
La API permite 120 solicitudes por minuto por clave, y ese techo es el mismo en todos los planes. Es generoso para la carga de solo lectura para la que se diseñó: consultar la cartera una vez por minuto usa menos del 1%, dejando holgura de sobra para comprobar señales. Si lo superas, la llamada devuelve 429 con el techo en el cuerpo: retrocede en lugar de reintentar al instante.
Algunos hábitos te mantienen bien dentro del límite:
- •No hagas polling más rápido de lo que necesitas. Las señales de EV se actualizan según la cadencia del cron del escáner, no de forma continua, así que hacer polling de
/signalscada pocos segundos solo gasta cuota devolviendo datos idénticos. Una vez por minuto es más que suficiente; una vez cada pocos minutos suele bastar. - •Prefiere webhooks para los eventos. Si haces polling para detectar *que algo ocurrió* —que una ballena se movió, que se disparó un stop-loss— estás usando la herramienta equivocada. Los webhooks (más abajo) te los empujan en el instante en que ocurren, con cero costo de polling.
- •Cachea dentro de un mismo ciclo. Si tres partes de tu pipeline necesitan el portafolio, obténlo una vez y pásalo entre ellas en lugar de llamar tres veces.
- •Aplica backoff ante errores. Si alguna vez te limitan (throttling), tómalo como una señal para ir más despacio, no para reintentar en un bucle ajustado.
El modelo mental: usa la API REST para obtener estado bajo demanda y usa webhooks para reaccionar a eventos. Confundir estos dos roles es la causa más común de cuota desperdiciada.
Suscripciones de webhook
Los webhooks invierten la dirección. En lugar de que tú le preguntes a Predite "¿algo nuevo?", Predite llama a *tu* URL en el momento en que algo ocurre. Como el payload es JSON simple sobre HTTPS POST, cualquier cosa que pueda recibir una solicitud HTTP funciona como destino: webhooks entrantes de Discord y Slack, plataformas de automatización como n8n y Zapier, o tu propio servidor.
Crear una suscripción
- Ve a Configuración → Webhooks (se requiere plan Bot).
- Haz clic en Agregar webhook y pega tu URL de destino. En producción debe ser
https://: el HTTP plano se rechaza. - Dale una etiqueta y selecciona qué tipos de evento debe recibir. Una suscripción debe escuchar al menos un evento.
- Guarda. Predite genera un secreto de firma y lo muestra en la vista de detalle de la suscripción.
El secreto se ve así:
``
whsec_4f8c...d2a1
``
(el prefijo whsec_ más 64 caracteres hexadecimales). Lo necesitarás para verificar las entregas: guárdalo junto con el destino, idealmente como una variable de entorno en el servicio receptor.
Tipos de evento
Hay seis tipos de evento suscribibles, y los seis se entregan hoy:
- •`ev_signal` — el escáner encontró una nueva oportunidad de EV por encima del umbral.
- •`arb_opportunity` — apareció una diferencia de arbitraje entre Polymarket y Kalshi en mercados equivalentes.
- •`stop_loss_triggered` — una de tus protecciones cruzó su umbral y disparó.
- •`bot_trade_executed` — uno de tus bots abrió o cerró una operación.
- •`whale_move` — una cartera rastreada tomó una posición de 25.000 USD o más. Evento global: lo recibe cada suscriptor, y cada movimiento se deduplica para que nunca llegue dos veces.
- •`resolution_imminent` — un mercado *en el que tienes posición* entró en las últimas 24 horas antes de resolverse. Por usuario, y enviado una vez por mercado.
Los dos últimos se podían suscribir pero eran mudos: sus fuentes son consultas puras, así que sin memoria de lo ya enviado un cron reemitiría los mismos eventos en cada ejecución. Esa memoria ya existe: es lo que los volvió entregables.
Suscribe cada destino solo a los eventos que le interesan. Tu pager de guardia probablemente quiera únicamente stop_loss_triggered; un canal de investigación podría querer whale_move y ev_signal; un flujo de automatización que rebalancea podría querer bot_trade_executed.
Payload y encabezados de entrega
Cada entrega es un POST HTTP con un cuerpo JSON con esta forma:
``json
{
"event": "ev_signal",
"timestamp": "2026-06-01T14:33:12.004Z",
"data": {
"market_title": "Will the ECB hold rates in June 2026?",
"direction": "NO",
"edge_pp": 9.2,
"market_price": 0.71,
"ai_price": 0.618
}
}
``
El campo event te indica el tipo, timestamp es el momento en que Predite lo despachó y data es el payload específico del evento. Junto con el cuerpo, Predite envía estos encabezados:
- •
X-Predite-Event— el tipo de evento, para que puedas enrutar sin parsear el cuerpo. - •
X-Predite-Timestamp— la hora de despacho (coincide con eltimestampdel cuerpo). - •
X-Predite-Signature— la firma HMAC-SHA256, con formatosha256=<hex>. - •
User-Agent: Predite-Webhooks/1.0.
Tu endpoint debe responder con cualquier estado 2xx rápidamente. La entrega expira tras 5 segundos, así que confirma rápido y haz el trabajo pesado de forma asíncrona: devuelve 200 y luego procesa.
Comportamiento de confiabilidad
La entrega no es de tipo "dispara y olvida" del lado de Predite. Las entregas fallidas (una respuesta que no sea 2xx, un timeout o un error de red) se registran, y los fallos transitorios 5xx/de red se reintentan con backoff exponencial. Si una suscripción acumula 10 fallos consecutivos, Predite la deshabilita automáticamente (estado disabled_by_failures) para dejar de golpear un endpoint muerto. Verás el motivo del fallo en Configuración → Webhooks, y volver a habilitar la suscripción reinicia el contador de fallos. Cada suscripción también registra el último éxito, el último fallo y el total de entregas, para que puedas auditar su salud de un vistazo.
Verificar las firmas de los webhooks
Como tu URL de webhook no es más que un endpoint HTTP, cualquiera que la descubra podría hacer POST de eventos falsos. El encabezado de firma es la forma de probar que una entrega realmente provino de Predite y no fue manipulada en tránsito.
El esquema es sencillo: Predite calcula HMAC-SHA256(raw_request_body, your_secret) y envía el digest hexadecimal en X-Predite-Signature como sha256=<digest>. De tu lado recalculas el mismo HMAC sobre el cuerpo crudo y sin parsear y los comparas. La parte del "cuerpo crudo" importa: si tu framework parsea el JSON y lo vuelves a serializar, los espacios en blanco y el orden de las claves pueden diferir y la firma no coincidirá. Captura los bytes antes de parsear.
Una comparación a prueba de timing en Python:
```python import hashlib import hmac
def verify(raw_body: bytes, signature_header: str, secret: str) -> bool: if not signature_header or not signature_header.startswith("sha256="): return False sent = signature_header.split("=", 1)[1] expected = hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(sent, expected) ```
Un receptor mínimo en Flask que rechaza cualquier cosa sin firmar:
```python import os from flask import Flask, request, abort
app = Flask(__name__) SECRET = os.environ["PREDITE_WEBHOOK_SECRET"]
@app.post("/predite-webhook") def receiver(): raw = request.get_data() # raw bytes, before JSON parsing if not verify(raw, request.headers.get("X-Predite-Signature", ""), SECRET): abort(401) payload = request.get_json() event = payload["event"] # route on event, then return fast handle(event, payload["data"]) return "", 200 ```
Usa siempre una comparación de tiempo constante (hmac.compare_digest, no ==) para no filtrar bytes de la firma a través del timing. Y rechaza la solicitud antes de hacer cualquier trabajo real si la verificación falla: esa única comprobación es la diferencia entre un pipeline confiable y una puerta abierta.
Construir un pipeline de alertas personalizado
Unámoslo todo con un ejemplo concreto: quieres que cada señal de EV de alta convicción y cada disparo de stop-loss lleguen a Slack, y quieres que los stop-loss además activen un flujo de trabajo de n8n que registre el evento y (opcionalmente) cierre la posición a través de tu propia integración con un bróker.
- Crea dos suscripciones de webhook en Configuración → Webhooks. Una apunta a la URL de tu webhook entrante de Slack y se suscribe a
ev_signal. La otra apunta a tu nodo de webhook de n8n y se suscribe astop_loss_triggered. Cada una obtiene su propio secreto. - Para Slack, el enfoque más limpio es un pequeño relay (una función serverless funciona bien): verifica la firma de Predite, formatea el
dataen un mensaje de Slack y lo publica en la URL de Slack. El relay te permite filtrar —por ejemplo, reenviar solo las señales dondedata.edge_pp >= 10— para que no satures el canal. Los mercados con un edge pequeño se descartan; solo los fuertes alertan al equipo. - Para n8n, coloca primero un nodo de trigger Webhook y luego un nodo Function que ejecute la misma comprobación HMAC contra el cuerpo crudo antes de que se ejecute cualquier cosa aguas abajo. Tras la verificación, ramifica según
datapara registrar el evento y llamar a tu bróker. Zapier sigue el mismo patrón, con un paso de Code que hace la verificación. - Agrega la API REST para obtener contexto. Cuando llega un evento
stop_loss_triggered, tu flujo de n8n puede llamar de inmediato aGET /api/v1/portfoliocon tu API key para obtener el conjunto completo de posiciones actuales, de modo que la alerta se enriquezca con P&L en vivo en lugar de solo los datos del disparador. Este es el patrón de lectura bajo demanda funcionando junto con el patrón de push. - Hazlo idempotente. Las redes reintentan; la misma entrega puede llegar dos veces. Indexa tus acciones aguas abajo con algo estable (el evento más su timestamp, o un ID dentro de
data) para que una entrega duplicada no actúe dos veces. - Monitorea el monitor. Revisa Configuración → Webhooks periódicamente en busca de
disabled_by_failures, y haz que tu relay registre cada firma rechazada: un pico repentino significa que tu secreto rotó o que alguien está sondeando tu endpoint.
Con eso en su lugar tienes un bucle cerrado: el escáner y el motor de riesgo de Predite empujan eventos en el instante en que se disparan, tus relays los verifican y filtran, y tus llamadas REST completan el estado en vivo bajo demanda, todo sin un solo bucle de polling gastando cuota.
Si estás en el plan Bot, la forma más rápida de sentir que esto encaja es ir a Configuración → Webhooks, apuntar una suscripción de prueba a un canal de Discord descartable, suscribirla a ev_signal y ver llegar la primera señal real como un POST JSON; luego conecta el fragmento de verificación de arriba y estarás a un relay de producción.