Contrato de eventos v1
El corazón del SDK. Solo cambios aditivos: agregar campos opcionales está permitido; quitar,
renombrar o cambiar tipos exige un v2 conviviendo con v1. Tipos de evento desconocidos se
IGNORAN (forward-compat), así que tu juego nunca se rompe por un evento nuevo.
Envelope
Todo mensaje, por cualquier transporte, es un JSON con este sobre:
{
"v": 1,
"id": "evt_01J9ZK3M7Q",
"type": "chat",
"ts": 1751990000000,
"data": { }
}
| Campo | Tipo | Descripción |
|---|---|---|
v | number | Versión del contrato. Siempre 1. |
id | string | Id único del evento (usa esto para deduplicar si reconectas). |
type | string | Tipo de evento. Tipos desconocidos se IGNORAN. |
ts | number | Epoch ms en que el agente capturó el evento. |
data | object | Payload según tipo. |
Objeto user (común a los eventos de viewer)
{
"userId": "6829385920134",
"username": "pepito_gamer",
"displayName": "Pepito 🎮",
"avatarUrl": "https://p16-sign.tiktokcdn..."
}
username es el uniqueId de TikTok (estable, sin @). avatarUrl puede venir null.
Eventos de live
chat — mensaje en el chat
{ "v": 1, "id": "evt_x1", "type": "chat", "ts": 1751990000000,
"data": { "user": { "...": "..." }, "message": "JOIN" } }
gift — regalo recibido
{ "v": 1, "id": "evt_x2", "type": "gift", "ts": 1751990001000,
"data": {
"user": { "...": "..." },
"giftId": 5655, "giftName": "Rose", "diamonds": 1,
"count": 3, "streakEnd": true,
"imageUrl": "https://..."
} }
count es la cantidad acumulada del streak; streakEnd: true indica que el streak cerró. Para
gifts apilables, cuenta SOLO cuando streakEnd es true. En v1 los gifts llegan SIEMPRE con
streakEnd: true (el agente resuelve el streak antes de emitir). La intensidad de un gift es
diamonds * count.
join — viewer entra al live
{ "data": { "user": { "...": "..." } } }
like — likes (agrupados)
{ "data": { "user": { "...": "..." }, "count": 15, "totalLikes": 5230 } }
follow — nuevo follower durante el live
{ "data": { "user": { "...": "..." } } }
share — viewer comparte el live
{ "data": { "user": { "...": "..." } } }
live_status — ciclo de vida del live
{ "data": { "status": "started", "roomId": "7301234..." } }
status es "started" o "ended".
Mensajes de control (del sistema al juego)
init — primer mensaje al conectar
{ "v": 1, "type": "init", "ts": 1751990000000,
"data": {
"app": { "appId": "com.dev.batalla-clanes", "version": "1.2.0" },
"live": { "active": true, "roomId": "7301234...", "host": "vureta" },
"config": { "comando_join": "JOIN", "vidas_iniciales": 3, "gift_revive": 5655 }
} }
config llega RESUELTA: los valores que el streamer guardó, con los defaults del manifest para
lo no configurado.
config_updated — hot-reload de config
{ "v": 1, "type": "config_updated", "ts": 1751990500000,
"data": { "config": { "comando_join": "UNIRME", "vidas_iniciales": 5 } } }
Se emite cuando el streamer guarda cambios con el juego conectado. Llega la config COMPLETA (no un diff).
ping / pong
En WebSocket el servidor envía {"v":1,"type":"ping"} cada 30 s; responde {"v":1,"type":"pong"}.
El long-poll no lo necesita.
Garantías y no-garantías
- Orden: en orden de captura por transporte local; por relay cloud se preserva dentro del cursor.
- Entrega: at-most-once en WebSocket (si te desconectas pierdes lo emitido mientras tanto); long-poll y relay tienen cursor con buffer corto (~60 s) para no perder ráfagas.
- Sin persistencia: el canal no es historial. Si necesitas acumular, acumula tú.
- Rate: sin throttling en v1. Un live grande puede emitir decenas de eventos/s; procesa barato o encola internamente.
Soporte inicial
La captura actual soporta desde el día uno: chat, gift, join, like, follow y
live_status. Aún no se emiten share ni like.data.totalLikes; user.avatarUrl puede venir
null. Como los tipos ausentes simplemente no llegan y el cliente ignora tipos desconocidos,
agregar soporte después es 100% compatible.