Tokens de viewer
POST /battles/{id}/viewer-tokens escopo: viewer:mint → 200
Um token de viewer é a credencial que um browser apresenta para assistir a uma batalha ao vivo. É a única credencial do SlotBattle que chega a código não confiável, e por isso ela é pequena, curta e com escopo de exatamente uma batalha.
{
"token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImsxIn0...",
"expires_at": "2026-08-03T03:55:42Z",
"ws_url": "wss://slotbattle.example.com/ws?battle_id=btl_CIkE8lctOrFDe1hJlG0T",
"ws_subprotocol": "slotbattle.jwt"
}
O que tem dentro
Um JWT assinado com Ed25519. Decodificado, de uma instância real:
// header
{ "alg": "EdDSA", "typ": "JWT", "kid": "k1" }
// claims
{
"iss": "slotbattle",
"aud": "slotbattle-ws",
"tenant_id": "acme",
"battle_id": "btl_CIkE8lctOrFDe1hJlG0T",
"sub": "docs verification",
"role": "viewer",
"iat": 1785728442,
"exp": 1785729342
}
O tempo de vida é de 15 minutos. battle_id está embutido, então um token de uma batalha é
inútil em outra. O kid nomeia a chave de assinatura, que é o que torna a rotação
sobrevivível. Veja Chaves e credenciais.
Quando você precisa desta rota
POST /battles e POST /battles/{id}/seats já retornam um token. Chame esta rota quando:
- O token expirou. Um lobby que ficou vinte minutos aberto sobreviveu ao próprio token.
- Um espectador novo chega. Alguém assistindo a uma batalha que não criou nem entrou.
- Você está re-renderizando. Um reload de página passados os 15 minutos.
Emita um por espectador. Eles são baratos, e o token é a única granularidade de revogação disponível: um token compartilhado por uma plateia não pode ser retirado de uma pessoa só.
Nunca coloque o token numa URL
O token pertence a um corpo de resposta que o seu front end lê, e depois ao header de
subprotocolo do WebSocket. Colocá-lo no ws_url como parâmetro de query, ou em qualquer URL,
vaza para log de acesso, log de proxy e histórico do browser.
A API nunca faz isso: ws_url carrega só battle_id, e o token é um campo separado.
Falhas
| Status | Código | Significado |
|---|---|---|
403 | forbidden | A chave não tem viewer:mint |
404 | not_found | Batalha inexistente para este casino |
500 | internal | A instância não tem chave de assinatura |
503 | unavailable | A instância não tem emissor de token, ou ainda está subindo |
O 500 é problema de configuração, não bug
{ "error": { "code": "internal", "message": "failed to mint viewer token" } }
A instância não tem chave de assinatura de viewer. Batalhas continuam abrindo e assentos
continuam enchendo; só a visualização ao vivo fica indisponível, e o ws_token fica ausente
das respostas de criação e de assento porque o campo é opcional.
Reporte ao seu host, citando essa mensagem exata. A chave de assinatura tem escopo de plataforma, já que uma chave assina para todo casino da instância, então configurá-la é trabalho deles.
Lidando com expiração num front end
O token sobrevive à maioria dos lobbies, mas não a todos. Um padrão que funciona:
- Use o
ws_tokenda resposta de criação ou de assento na primeira conexão. - Guarde
ws_token_expires_at. Reemita quando faltar menos de um minuto para expirar, ou quando o socket fechar e for preciso reconectar. - Passe a emissão pelo seu backend, que guarda a chave de API. Nunca chame o SlotBattle do browser.
O tutorial implementa exatamente isso.
Próximo
- WebSocket: apresentando o token no handshake
- Chaves e credenciais: rotação e a janela dela