# Tiempo real y webhooks

> Webhooks firmados, cómo verificarlos y el estado del endpoint de streaming.

Source: https://docs.botradix.com/es/api-realtime/

Los webhooks entregan a tu servidor los eventos de la cuenta de tu wallet en cuanto BotRadix los indexa desde la cadena, para que no tengas que consultar sin parar.

## Crear un webhook

Los webhooks se gestionan con una sesión de desarrollador (ver [API: visión general y autenticación](https://docs.botradix.com/es/api/)):

```bash
curl -X POST https://app.botradix.com/api/dex/developer/webhooks \
  -H "Authorization: Bearer $DEVELOPER_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/botradix/webhook","events":["order.filled","position.changed","liquidation"]}'
```

| Regla | Valor |
|---|---|
| URL | HTTPS en el puerto 443 u 8443, sin credenciales incrustadas, hasta 512 caracteres y resolviendo a IP públicas |
| Límite | Hasta 8 webhooks activos por wallet |
| Eventos | Una lista de nombres de evento, o `["*"]` para todos. Por defecto: `transaction.confirmed` y `transaction.reverted` |
| Secreto | `signing_secret` (empieza por `brx_whsec_`) se devuelve **una sola vez**, al crear el webhook |
| Baja | `DELETE /api/dex/developer/webhooks/{webhook_id}` desactiva la suscripción |

## Eventos

| Evento | Cuándo |
|---|---|
| `order.accepted` | Una orden fue aceptada en cadena |
| `order.filled` | Una orden se ejecutó por completo |
| `order.partially_filled` | Una orden se ejecutó en parte |
| `order.cancelled` | Una orden fue cancelada |
| `position.changed` | Una posición se abrió, aumentó, redujo, cerró o invirtió |
| `liquidation` | Una posición fue liquidada |
| `funding.charged` / `funding.credited` | Se pagó o se cobró funding |
| `collateral.changed` | Un depósito o un retiro cambió tu colateral |
| `transaction.confirmed` / `transaction.reverted` | Una transacción conocida por BotRadix alcanzó la finalidad, con éxito o sin él |
| `chain.reorg` | Una reorganización de la cadena cambió datos ya notificados |
| `transaction.pending` | Solo como corrección tras una reorganización (con `revoked_event_id`) |

`liquidation.warning` se acepta en las suscripciones pero está reservado: hoy no se emite.

> [!NOTE]
> Los eventos `transaction.*` se envían para transacciones cuyo hash conoce BotRadix: las enviadas por el relay o por el ejecutor delegado, y aquellas cuyo hash comunicaste a `/api/dex/actions/receipt`.

## Entrega

Cada entrega es un `POST` HTTPS con un cuerpo JSON:

```json
{ "id": "…", "type": "position.changed", "created_at": 1790950000, "data": { … } }
```

| Cabecera | Contenido |
|---|---|
| `X-BotRadix-Event` | Tipo de evento |
| `X-BotRadix-Event-ID` | ID único y estable del evento (hasta 96 caracteres, igual que `id` en el cuerpo). Úsalo para descartar duplicados |
| `X-BotRadix-Timestamp` | Hora Unix (segundos) de este intento de entrega |
| `X-BotRadix-Signature` | `v1=` seguido de la firma HMAC-SHA256 en hexadecimal |
| `User-Agent` | `BotRadix-DEX-Webhook/1.0` |

- La entrega es **al menos una vez**: un mismo evento puede llegar más de una vez. Descarta duplicados por `X-BotRadix-Event-ID`.
- Responde con cualquier código `2xx` en menos de 4 segundos. No se siguen redirecciones.
- Una entrega fallida se reintenta hasta 4 intentos en total, esperando 2, 4 y 8 segundos.
- Los eventos pueden llegar desordenados; usa `created_at` y los propios datos para ordenarlos.

## Verificar la firma

La firma es un HMAC-SHA256 de `timestamp + "." + cuerpo_sin_procesar`, con tu `signing_secret` completo como clave. Verifícala siempre contra el cuerpo **sin procesar**, antes de interpretar el JSON, y rechaza marcas de tiempo antiguas.

```javascript
// Node.js (en Express, usa express.raw({ type: "application/json" }) en esta ruta)
import crypto from "node:crypto";

export function verifyBotRadixWebhook(rawBody, headers, secret, toleranceSeconds = 300) {
  const timestamp = headers["x-botradix-timestamp"];
  const received = headers["x-botradix-signature"] || "";
  if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false;
  const expected = "v1=" + crypto.createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest("hex");
  return received.length === expected.length && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
}
```

```python
# Python
import hashlib, hmac, time

def verify_botradix_webhook(raw_body: bytes, headers, secret: str, tolerance: int = 300) -> bool:
    timestamp = headers.get("X-BotRadix-Timestamp", "")
    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance:
        return False
    expected = "v1=" + hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(headers.get("X-BotRadix-Signature", ""), expected)
```

## Stream Server-Sent Events

`GET /api/dex/v1/stream?pair_id={id}&interval=1m` está definido como un stream privado con snapshots de mercado y de cuenta y eventos de ciclo de vida, un latido cada 12 segundos y reanudación con `Last-Event-ID`. **Aún no está activado en el servicio público** y responde `503 REALTIME_STREAM_ASYNC_WORKER_REQUIRED`. Mientras tanto, usa webhooks para los eventos de cuenta y los endpoints de lectura para los datos de mercado. Esta página se actualizará cuando se active.
