Guia de Eventos en Tiempo Real (SSE)
Transmita precios FX en vivo, estados de transferencias, actualizaciones de ordenes y cambios de saldo a su aplicacion usando Server-Sent Events en lugar de polling.
Descripcion General
La plataforma ARi envia actualizaciones en tiempo real a su aplicacion a traves de una conexion HTTP persistente usando Server-Sent Events (SSE). En lugar de consultar endpoints cada pocos segundos, abra una sola conexion y reciba eventos en el instante en que ocurren — cambios de precio, transferencias completadas, ordenes ejecutadas y actualizaciones de saldo.
| Caracteristica | Detalles |
|---|---|
| Endpoint | GET /api/v1/events/stream |
| Protocolo | Server-Sent Events (text/event-stream) |
| Autenticacion | Header Ocp-Apim-Subscription-Key |
| Eventos Broadcast | price.update — cambios de tasa FX en vivo (todos los clientes) |
| Eventos por Usuario | transfer.status, fxorder.status, balance.updated — actualizaciones por usuario |
| Reconexion | Replay automatico via header Last-Event-ID |
SSE vs Polling
| SSE | Polling | |
|---|---|---|
| Latencia | Push instantaneo | Depende del intervalo de consulta |
| Ancho de Banda | Una conexion abierta | Solicitudes repetidas, mayormente vacias |
| Complejidad | Manejar eventos entrantes | Administrar timers y deduplicacion |
| Ideal para | Dashboards en vivo, UIs de trading | Integraciones simples, trabajos batch |
Alcance de Eventos
Los eventos se dividen en dos categorias segun quien los recibe:
+---------------------------------------------------------------+
| ALCANCE DE EVENTOS |
+---------------------------------------------------------------+
BROADCAST (price.update):
┌──────────────┐
│ ARi API │ ──> Cliente A
│ │ ──> Cliente B Todos los clientes
│ │ ──> Cliente C reciben el evento
└──────────────┘
POR USUARIO (transfer.status, fxorder.status, ...):
┌──────────────┐
│ ARi API │ ──> Cliente A ✓ (externalUserId coincide)
│ │ Cliente B ✗ (usuario diferente)
│ │ Cliente C ✗ (usuario diferente)
└──────────────┘
+---------------------------------------------------------------+| Alcance | Eventos | Entrega |
|---|---|---|
| Broadcast | price.update |
Todos los clientes conectados |
| Por usuario | transfer.status, fxorder.status, balance.updated, y mas |
Solo la conexion que coincide con el usuario destino |
Para partners M2M, los eventos por usuario se filtran por el externalUserId configurado en su perfil de partner. Usted recibe unicamente eventos que pertenecen a su usuario.
¿Multiples usuarios? Si necesita eventos para multiples usuarios, abra una conexion SSE separada para cada
externalUserId. Cada conexion tiene su propio alcance de eventos.
Inicio Rapido
Requisitos Previos
Antes de conectarse al stream de eventos, asegurese de tener:
- Clave de Suscripcion API — Obtenga desde el Portal de Desarrolladores
- Cliente HTTP con soporte de streaming —
curl,fetchconReadableStream, orequestsconstream=True
Importante: La API nativa del navegador
EventSourceno puede enviar headers personalizados. Debe usarfetcho un cliente HTTP que soporte headers personalizados en solicitudes de streaming.
Pruebe ahora: Use el Tester de Stream SSE para probar su conexion directamente desde el portal — sin necesidad de codigo.
Prueba de Conexion en Sandbox
Abra una terminal y ejecute:
curl -N "https://sandbox-api.ariari.xyz/api/v1/events/stream" \
-H "Ocp-Apim-Subscription-Key: SU_CLAVE" \
-H "Accept: text/event-stream"Deberia ver:
event: connected
data: {"status":"connected"}
id: conn-abc123
event: heartbeat
data:
id: hb-1
event: heartbeat
data:
id: hb-2El evento connected confirma que su stream esta activo. Los heartbeats llegan cada 25 segundos para mantener la conexion viva.
Como Funciona SSE
+---------------------------------------------------------------+
| FLUJO DE CONEXION SSE |
+---------------------------------------------------------------+
Paso 1: EL CLIENTE ABRE LA CONEXION
┌──────────────┐ ┌──────────────┐
│ Cliente │ ──GET /stream───> │ ARi API │
│ (Su App) │ │ Gateway │
└──────────────┘ └──────┬───────┘
│
Paso 2: EL SERVIDOR CONFIRMA │
┌──────────────┐ ┌──────┴───────┐
│ Cliente │ <──200 OK──────── │ ARi API │
│ (Su App) │ text/event-stream │ Gateway │
└──────────────┘ └──────┬───────┘
│
Paso 3: EVENTOS EN TIEMPO REAL │
┌──────────────┐ ┌──────┴───────┐
│ Cliente │ <──price.update── │ ARi API │
│ (Su App) │ <──heartbeat───── │ Gateway │
│ │ <──price.update── │ │
└──────────────┘ └──────────────┘
La conexion permanece abierta hasta que usted la cierre,
o un evento de ciclo de vida (auth_expired, server_shutdown)
le indique reconectarse.
+---------------------------------------------------------------+Formato de Eventos
Cada mensaje SSE tiene tres campos:
event: <tipo>
id: <id-unico>
data: <payload-json>Los campos estan separados por saltos de linea. Cada mensaje termina con una linea en blanco. Ejemplo:
event: price.update
id: msg-a1b2c3d4-5678
data: {"event_type":"fx.rate_changed","buy":510.50,"sell":520.30}
| Campo | Descripcion |
|---|---|
event |
Tipo de evento — determina que handler lo procesa |
id |
Identificador unico — usado para replay en reconexion |
data |
Payload JSON — el contenido del evento |
Eventos Broadcast
Los eventos broadcast se entregan a todos los clientes conectados sin importar la identidad.
Actualizaciones de Precio
El evento price.update se dispara cada vez que las tasas FX cambian. Este es el evento principal para construir UIs de trading en tiempo real y pantallas de tasas.
Evento: `price.update`
Se dispara cuando una tasa FX cambia en la plataforma.
+------------------------------------------+
| event: price.update |
| id: msg-a1b2c3d4-5678 |
| data: { |
| "event_type": "fx.rate_changed", |
| "buy": 510.50, |
| "sell": 520.30 |
| } |
+------------------------------------------+| Campo | Tipo | Descripcion |
|---|---|---|
event_type |
string | Siempre "fx.rate_changed" |
buy |
number | Tasa de compra actual |
sell |
number | Tasa de venta actual |
Como Usarlo
+---------------------------------------------------------------+
| REEMPLAZAR POLLING CON SSE |
+---------------------------------------------------------------+
ANTES (Polling):
┌──────────────┐ ┌──────────────┐
│ Cliente │ ─GET───> │ /getQuote │
│ │ <─200─── │ │ Cada 5s
│ │ ─GET───> │ │ (desperdicio
│ │ <─200─── │ │ cuando las tasas
│ │ ─GET───> │ │ no cambian)
│ │ <─200─── │ │
└──────────────┘ └──────────────┘
DESPUES (SSE):
┌──────────────┐ ┌──────────────┐
│ Cliente │ ─GET───> │ /stream │
│ │ <─200─── │ │ Una conexion,
│ │ │ │ eventos enviados
│ │ <─event─ │ │ solo cuando las
│ │ │ │ tasas cambian
│ │ <─event─ │ │
└──────────────┘ └──────────────┘
+---------------------------------------------------------------+Eventos por Usuario
Los eventos por usuario se entregan unicamente a la conexion cuyo externalUserId coincide con el usuario destino del evento. Estos eventos rastrean el ciclo de vida de sus transferencias, ordenes FX y saldos de cuenta.
Referencia de Tipos de Evento
| Evento | Descripcion |
|---|---|
transfer.status |
Estado de transferencia cambio (PROCESSING, COMPLETED, FAILED) |
transfer.completed |
Transferencia completada exitosamente |
transfer.failed |
Transferencia fallida |
transfer.leg_progress |
Progreso de tramo de transferencia |
fxorder.status |
Estado de orden FX cambio |
fxorder.created |
Nueva orden FX creada |
balance.updated |
Saldo de cuenta cambio |
Eventos de Transferencia
Los eventos de transferencia se disparan mientras sus transferencias avanzan por el pipeline de procesamiento.
Evento: `transfer.status`
Se dispara cuando una transferencia cambia de estado.
+------------------------------------------+
| event: transfer.status |
| id: msg-e5f6g7h8-9012 |
| data: { |
| "reference_code": "TRF-20260310-001", |
| "status": "COMPLETED", |
| "settled_amount": 500000, |
| "fee_amount": 2500, |
| "completed_at": "2026-03-10T14:30:00Z" |
| } |
+------------------------------------------+| Campo | Tipo | Descripcion |
|---|---|---|
reference_code |
string | Codigo de referencia de la transferencia |
status |
string | Nuevo estado: PROCESSING, COMPLETED, FAILED |
status_reason |
string? | Razon del cambio de estado (si aplica) |
settled_amount |
number? | Monto liquidado final |
fee_amount |
number? | Comision cobrada |
completed_at |
string? | Timestamp de completado ISO 8601 |
Evento: `transfer.completed` / `transfer.failed`
Eventos terminales que indican el resultado final de una transferencia. El payload sigue la misma estructura que transfer.status.
Evento: `transfer.leg_progress`
Se dispara cuando una transferencia multi-tramo completa un paso de procesamiento.
| Campo | Tipo | Descripcion |
|---|---|---|
reference_code |
string | Codigo de referencia de la transferencia |
leg_number |
number | Numero de tramo actual |
total_legs |
number | Total de tramos en la transferencia |
leg_type |
string | Tipo de tramo (ej., debito, credito, SINPE) |
leg_status |
string | Estado del tramo |
timestamp |
string | Timestamp ISO 8601 |
Eventos de Ordenes FX
Los eventos de ordenes FX se disparan mientras sus ordenes se procesan en la plataforma.
Evento: `fxorder.status`
Se dispara en cada transicion del estado de una orden FX.
+------------------------------------------+
| event: fxorder.status |
| id: msg-i9j0k1l2-3456 |
| data: { |
| "reference_code": "FXT-20260310-000042",|
| "status": "FILLED", |
| "rate": "512.750000", |
| "amount": "1000.00", |
| "converted_amount": "512750.00" |
| } |
+------------------------------------------+| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
reference_code |
string | si | Codigo de referencia de la orden. Formato: FXT-YYYYMMDD-NNNNNN. |
status |
string | si | Nuevo estado. Uno de PENDING_NEW, NEW, ACCEPTED, FILLED, REJECTED, CANCELED (enum canonico `FxOrderStatus`). |
rate |
string? | no | Tipo de cambio final con tier mezclado (6 decimales). Se completa cuando status=FILLED. |
amount |
string? | no | Importe del lado origen. |
converted_amount |
string? | no | Importe del lado destino. Se completa cuando status=FILLED. |
Mapa de ciclo de vida → evento
Una sola orden FX emite un fxorder.created mas un fxorder.status por cada transicion. La cascada completa de la ruta feliz es:
solicitud createOrder
│
├─ fxorder.created (status=PENDING_NEW)
├─ fxorder.status NEW
├─ fxorder.status ACCEPTED
├─ fxorder.status FILLED ───┐
└─ balance.updated ────────┘ (debito origen + credito destino liquidados)Las rutas de falla cortocircuitan la cascada:
| Transicion | Disparador | Eventos subsiguientes |
|---|---|---|
* → REJECTED |
Validacion o rechazo aguas arriba en cualquier estado pre-FILLED | fxorder.status (REJECTED). Sin balance.updated. |
* → CANCELED |
Llamada de cancelacion del socio o regla del sistema antes del fill | fxorder.status (CANCELED). Sin balance.updated. |
No existe estado EXPIRED — la expiracion de la cotizacion antes de createOrder se devuelve sincronicamente como `DB-FX-410`; una cotizacion que expira durante la ventana pre-fill de una orden se resuelve como REJECTED.
Evento: `fxorder.created`
Se dispara cuando se crea una nueva orden FX. El payload refleja la forma de respuesta de `FxOrder` createOrder (estado inicial status=PENDING_NEW, rate=null, converted_amount=null).
Eventos de Saldo
Evento: `balance.updated`
Se dispara cuando un saldo de cuenta cambia — tipicamente despues de una ejecucion de orden FX o completado de transferencia.
+------------------------------------------+
| event: balance.updated |
| id: msg-m3n4o5p6-7890 |
| data: { |
| "event_type": "balance.updated", |
| "reference_code": "BAL-20260310-001" |
| } |
+------------------------------------------+Use este evento como disparador para refrescar su pantalla de saldo llamando a la API de saldos.
Eventos de Trading (Avanzado)
Estos eventos estan disponibles para partners con integracion de trading habilitada.
| Evento | Descripcion |
|---|---|
trading_order.status |
Estado de orden de trading cambio |
trading.order_filled |
Orden completamente ejecutada |
trading.order_rejected |
Orden rechazada |
trading.order_canceled |
Orden cancelada |
trading.order_expired |
Orden expirada |
trading.order_partially_filled |
Orden parcialmente ejecutada |
Los eventos de ordenes de trading llevan order_id, reference_code, status y (para fills) filled_qty + filled_avg_price. A diferencia de los eventos de ordenes FX — que describen una sola conversion — los payloads de trading rastrean fills parciales contra una cantidad, por lo que el conjunto de campos es distinto por diseno.
Eventos de Ciclo de Vida
Estos eventos gestionan el ciclo de vida de la conexion. No son datos de negocio — manejelos para mantener su conexion saludable.
| Evento | Data | Accion del Cliente |
|---|---|---|
connected |
{"status":"connected"} |
El stream esta activo. Comience a procesar eventos. |
heartbeat |
(vacio) | No se requiere accion. Mantiene la conexion viva a traves de proxies. |
auth_expired |
{"reason":"token_expired"} |
Refresque sus credenciales y reconectese. |
server_shutdown |
{"reason":"shutdown"} |
Reconectese inmediatamente — el servidor se esta reiniciando. |
Flujo de Ciclo de Vida
+---------------------------------------------------------------+
| CICLO DE VIDA DE CONEXION |
+---------------------------------------------------------------+
CONECTAR ──> connected ──> heartbeat (25s) ──> heartbeat ...
│
├──> price.update (broadcast)
├──> transfer.status (por usuario)
├──> fxorder.status (por usuario)
├──> balance.updated (por usuario)
│
├──> auth_expired ──> RECONECTAR
│ (con clave nueva)
│
└──> server_shutdown ──> RECONECTAR
(inmediatamente)
+---------------------------------------------------------------+Reconexion y Replay
Si su conexion se interrumpe, reconectese con el header Last-Event-ID para reproducir eventos perdidos. El servidor almacena en buffer los ultimos 100 eventos.
+---------------------------------------------------------------+
| RECONEXION CON REPLAY |
+---------------------------------------------------------------+
1. La conexion se interrumpe (red, reinicio de servidor, etc.)
┌──────────────┐
│ Cliente │ ──X── (desconectado)
└──────────────┘
2. El cliente se reconecta con el ultimo ID de evento conocido
┌──────────────┐ ┌──────────────┐
│ Cliente │ ──GET /stream───> │ ARi API │
│ │ Last-Event-ID: │ │
│ │ msg-a1b2c3d4 │ │
└──────────────┘ └──────┬───────┘
│
3. El servidor reproduce eventos perdidos, luego reanuda en vivo
┌──────────────┐ ┌──────┴───────┐
│ Cliente │ <──evt perdido──── │ ARi API │
│ │ <──evt perdido──── │ │
│ │ <──connected────── │ │
│ │ <──eventos vivos── │ │
└──────────────┘ └──────────────┘
+---------------------------------------------------------------+cURL: Reconectar con Last-Event-ID
curl -N "https://sandbox-api.ariari.xyz/api/v1/events/stream" \
-H "Ocp-Apim-Subscription-Key: SU_CLAVE" \
-H "Accept: text/event-stream" \
-H "Last-Event-ID: msg-a1b2c3d4-5678"Nota: El buffer de replay almacena hasta 100 eventos. Si estuvo desconectado por mas tiempo, algunos eventos pueden haberse perdido. Use un fallback de polling para datos criticos que no deben perderse.
Limites de Conexion y Rate Limits
| Limite | Valor | Descripcion |
|---|---|---|
| Conexiones por usuario | 3 | Maximo de conexiones SSE concurrentes por clave de suscripcion |
| Tasa de reconexion | 10/min | Maximo de intentos de reconexion por minuto |
| Intervalo de heartbeat | 25 segundos | Senal de keep-alive |
| Buffer de replay | 100 eventos | Eventos disponibles para replay via Last-Event-ID |
Exceder el limite de conexiones retorna 429 Too Many Requests.
Headers Requeridos
| Header | Requerido | Descripcion |
|---|---|---|
Ocp-Apim-Subscription-Key |
Si | Su clave de suscripcion API |
Accept |
Recomendado | text/event-stream |
Last-Event-ID |
No | Ultimo ID de evento recibido — activa replay de eventos perdidos |
Manejo de Errores
Errores Comunes
| Estado | Error | Causa | Solucion |
|---|---|---|---|
| 401 | Unauthorized | Clave de suscripcion faltante o invalida | Verifique su clave en el Portal de Desarrolladores |
| 429 | Too Many Requests | Limite de conexiones excedido (3/usuario) o tasa de reconexion excedida (10/min) | Cierre conexiones no utilizadas; use backoff exponencial |
Formato de Respuesta de Error
Los errores siguen RFC 7807 Problem Details:
{
"type": "about:blank",
"title": "Too Many Requests",
"status": 429,
"detail": "Maximum concurrent connections exceeded",
"trace_id": "0HN4G6LJN5FT0:00000001"
}Tip: Guarde el trace_id al contactar soporte.
Ejemplos de Codigo
cURL: Abrir Stream
curl -N "https://api.ariari.com/api/v1/events/stream" \
-H "Ocp-Apim-Subscription-Key: SU_CLAVE" \
-H "Accept: text/event-stream"JavaScript: Fetch + ReadableStream
async function connectSSE(apiKey) {
let lastEventId = '';
async function connect() {
const headers = {
'Ocp-Apim-Subscription-Key': apiKey,
'Accept': 'text/event-stream',
};
if (lastEventId) {
headers['Last-Event-ID'] = lastEventId;
}
const response = await fetch(
'https://api.ariari.com/api/v1/events/stream',
{ headers }
);
if (!response.ok) {
throw new Error(`Conexion SSE fallida: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const messages = buffer.split('\n\n');
buffer = messages.pop(); // Mantener mensaje incompleto en buffer
for (const message of messages) {
const event = parseSSE(message);
if (!event) continue;
if (event.id) lastEventId = event.id;
switch (event.type) {
case 'price.update':
console.log('Tasa cambio:', event.data);
// Actualice su UI con event.data.buy / event.data.sell
break;
case 'transfer.status':
case 'transfer.completed':
case 'transfer.failed':
console.log(`Transferencia ${event.data.reference_code}: ${event.data.status}`);
// Actualice el estado de la transferencia en su UI
break;
case 'fxorder.status':
console.log(`Orden FX ${event.data.reference_code}: ${event.data.status}`);
break;
case 'balance.updated':
console.log('Saldo cambio — refrescando...');
// Dispare un refresh de saldo desde la API
break;
case 'auth_expired':
console.warn('Auth expirado — reconectando...');
reader.cancel();
return connect(); // Reconectar con credenciales frescas
case 'server_shutdown':
console.warn('Servidor reiniciando — reconectando...');
reader.cancel();
return connect();
}
}
}
// Conexion cerrada — reconectar con backoff
setTimeout(connect, 1000);
}
connect();
}
function parseSSE(raw) {
const lines = raw.split('\n');
const event = {};
for (const line of lines) {
if (line.startsWith('event: ')) event.type = line.slice(7);
else if (line.startsWith('id: ')) event.id = line.slice(4);
else if (line.startsWith('data: ')) {
try {
event.data = JSON.parse(line.slice(6));
} catch {
event.data = line.slice(6);
}
}
}
return event.type ? event : null;
}Python: requests con Streaming
import requests
import json
import time
def connect_sse(api_key, base_url="https://api.ariari.com"):
last_event_id = None
backoff = 1
while True:
headers = {
"Ocp-Apim-Subscription-Key": api_key,
"Accept": "text/event-stream",
}
if last_event_id:
headers["Last-Event-ID"] = last_event_id
try:
with requests.get(
f"{base_url}/api/v1/events/stream",
headers=headers,
stream=True,
timeout=(10, None), # 10s conexion, sin timeout de lectura
) as resp:
resp.raise_for_status()
backoff = 1 # Resetear en conexion exitosa
buffer = ""
for chunk in resp.iter_content(decode_unicode=True):
buffer += chunk
while "\n\n" in buffer:
message, buffer = buffer.split("\n\n", 1)
event = parse_sse(message)
if not event:
continue
if event.get("id"):
last_event_id = event["id"]
if event["type"] == "price.update":
print(f"Tasa: compra={event['data']['buy']}, venta={event['data']['sell']}")
elif event["type"] in ("transfer.status", "transfer.completed", "transfer.failed"):
print(f"Transferencia {event['data']['reference_code']}: {event['data']['status']}")
elif event["type"] == "fxorder.status":
print(f"Orden FX {event['data']['reference_code']}: {event['data']['status']}")
elif event["type"] == "balance.updated":
print("Saldo cambio — refrescando...")
elif event["type"] == "auth_expired":
print("Auth expirado — reconectando...")
break
elif event["type"] == "server_shutdown":
print("Servidor reiniciando — reconectando...")
break
except requests.exceptions.RequestException as e:
print(f"Error de conexion: {e}")
# Backoff exponencial: 1s, 2s, 4s, ... max 30s
time.sleep(min(backoff, 30))
backoff *= 2
def parse_sse(raw):
event = {}
for line in raw.strip().split("\n"):
if line.startswith("event: "):
event["type"] = line[7:]
elif line.startswith("id: "):
event["id"] = line[4:]
elif line.startswith("data: "):
try:
event["data"] = json.loads(line[6:])
except json.JSONDecodeError:
event["data"] = line[6:]
return event if "type" in event else NoneMejores Practicas
- Implemente backoff exponencial en reconexion (1s, 2s, 4s... max 30s)
- Rastree
Last-Event-ID— persista el ultimo ID recibido para poder reproducir eventos perdidos al reconectar - Deduplique por ID de evento despues de reconexion — el buffer de replay puede solaparse con eventos que ya proceso
- NO use la API nativa
EventSource— no puede enviar headers personalizados (Ocp-Apim-Subscription-Key) - Maneje eventos de ciclo de vida —
auth_expiredsignifica refrescar credenciales,server_shutdownsignifica reconectar inmediatamente - Mantenga un fallback de polling — para datos criticos, consulte endpoints de estado (
/transfers/{id},/fxorder/{id}) periodicamente como red de seguridad - Limite conexiones abiertas — tiene 3 maximo por clave de suscripcion; cierre streams no utilizados
- Filtre por dominio de evento — si solo le interesan transferencias, verifique
event.type.startsWith('transfer.')e ignore el resto - Una conexion por usuario — partners M2M que necesiten eventos para multiples usuarios deben abrir conexiones separadas por
externalUserId - Ignore
heartbeaten logica de negocio — los heartbeats son senales de infraestructura, no datos de negocio
Flujo de Decision: SSE vs Webhooks vs Polling
+---------------------------------------------------------------+
| ¿QUE PATRON DE INTEGRACION? |
+---------------------------------------------------------------+
Inicio
|
v
+-----------------------------+
| ¿Necesita actualizaciones |
| en tiempo real en un |
| navegador o dashboard? |
+-----------------------------+
| |
SI NO
| |
v v
+------------+ +-----------------------------+
| Use | | ¿Necesita notificaciones |
| SSE | | push servidor-a-servidor? |
+------------+ +-----------------------------+
| |
SI NO
| |
v v
+------------+ +------------+
| Use | | Use |
| Webhooks | | Polling |
+------------+ +------------+
+---------------------------------------------------------------+
| SSE: Streaming en tiempo real a navegadores/apps & M2M |
| (esta guia) |
| Webhooks: Push a su endpoint de servidor (/docs/webhooks) |
| Polling: Solicitudes GET con timer (/apisuite) |
+---------------------------------------------------------------+Proximos Pasos
- Obtener Claves API — Comience a construir hoy
- Ordenes FX — Operaciones de cambio de divisas
- Transferencias — Transferencias en la misma moneda
- Referencia API — Documentacion completa de endpoints