Eventos
Toda mudança relevante numa batalha publica um evento. O mesmo vocabulário alimenta três consumidores, e cada um enxerga um subconjunto diferente.
O envelope
Todo evento, em todo canal, tem o mesmo formato:
{
"type": "spin.started",
"scope": "user",
"battle_id": "btl_01J8ZQ...",
"user_id": "seat_2",
"is_bot": false,
"ts": "2026-08-03T02:14:07.113Z",
"data": { }
}
| Campo | Notas |
|---|---|
type | Do vocabulário abaixo |
scope | battle ou user: se descreve a batalha inteira ou um assento |
battle_id | Sempre presente |
user_id | Presente em eventos de escopo user. A referência do assento |
is_bot | Presente em eventos de escopo user |
ts | UTC, RFC 3339 |
data | Payload específico do tipo. Ausente quando o tipo não carrega nenhum |
O vocabulário
Escopo de batalha: a batalha inteira
| Tipo | Emitido quando |
|---|---|
lobby.updated | Um assento mudou: alguém entrou, saiu, promoveu, ou bots foram sentados |
battle.started | A batalha saiu de OPEN e os assentos foram despachados |
battle.completed | Todos os assentos reportaram |
battle.failed | O lançamento falhou, um assento falhou sem recuperação, ou uma janela expirou |
battle.cancelled | O criador cancelou ou saiu, ou a janela do lobby expirou em OPEN |
Escopo de usuário: um assento
| Tipo | Emitido quando |
|---|---|
user.started | O gravador do assento começou o trabalho |
browser.ready | O browser headless carregou o jogo |
capture.started | A captura de vídeo começou |
capture.armed | O bônus começou, e a captura está gravando a rodada |
click.started / click.done / click.failed | Uma interação roteirizada com o jogo |
spin.started | Um giro começou |
segment.new | Um novo segmento de vídeo HLS está disponível |
segment.failed | Um segmento não pôde ser produzido ou enviado |
playlist.update | A playlist HLS mudou |
user.completed | O assento terminou com resultado |
user.failed | O assento falhou |
user.stopped | O assento foi parado antes de terminar |
Só oito eventos chegam a um browser
O feed WebSocket é uma visão filtrada do vocabulário. Exatamente estes oito são públicos:
| Públicos no WebSocket | |
|---|---|
lobby.updated | battle.started |
battle.completed | battle.failed |
battle.cancelled | spin.started |
segment.new | playlist.update |
Todo o resto, incluindo browser.ready, capture.*, click.*, user.* e segment.failed, é
interno. Existe para operadores e para a coordenação do próprio gravador, e nenhum token de
viewer vai expor isso.
Construa o seu front end contra esses oito. lobby.updated e battle.* dirigem o estado,
spin.started dirige a animação por assento, e segment.new e playlist.update dirigem o
player de vídeo.
user.completed não é públicoDados terminais de assento chegam ao seu backend pelo webhook, que é assinado e entregue de forma durável. Publicar desfechos por assento para todo browser conectado colocaria resultados nas mãos da plateia antes da sua própria liquidação, por um canal mais fraco. O evento terminal da batalha carrega o que a plateia pode saber.
Três consumidores, três subconjuntos
| Consumidor | Recebe | Transporte |
|---|---|---|
| Browsers dos espectadores | Os oito tipos públicos, de uma batalha | WebSocket, token de viewer |
| Seu backend | Só eventos terminais | Webhook assinado, entrega durável |
| A própria instância | Tudo | Pub/sub no Redis, interno |
Trate o webhook como fonte da verdade para resultados, não o WebSocket. O WebSocket é uma visão ao vivo que um espectador pode ter perdido, entrado tarde ou sido desconectado dela. O webhook retenta e é idempotente.
O que nunca aparece em nenhum evento
A URL de sessão de jogo. Nem em data, nem numa linha de log, nem em nenhum canal, nem em
nenhuma resposta HTTP. Ela é criptografada em repouso e nunca cruza a fronteira do processo, e
o teste ponta a ponta afirma a ausência dela.
Uma integração que precisa dela está atravessando a fronteira. Veja O que o SlotBattle não faz.
Ordem e entrega
Os eventos são publicados conforme acontecem e entregues em melhor esforço pelo WebSocket. Não há replay: um espectador que conecta no meio da batalha vê o que acontece dali em diante, não o histórico. Projete o front end para que uma conexão nova renderize a partir do snapshot atual da batalha mais os eventos novos, em vez de assumir que viu a sequência desde o começo.
A entrega terminal por webhook é o oposto: durável, retentada e idempotente por
battle_id : status : version, então eventos terminais duplicados nunca geram dois POSTs.
Próximo
- WebSocket: conectar, autenticar e reconectar
- Webhooks: payload, assinatura, retentativas
- Ciclo de vida da batalha: o que cada transição significa