Webhooks
Um POST assinado por batalha, quando ela acaba. É a autoridade sobre o resultado, não o feed WebSocket, que é uma visão ao vivo que um espectador pode ter perdido.
A requisição
POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-Slotbattle-Sign: 9f2c4a...
{
"battle_id": "btl_CIkE8lctOrFDe1hJlG0T",
"tenant_id": "acme",
"status": "completed",
"seats": [
{
"user_id": "usr_s0",
"seat_ref": "usr_s0",
"is_bot": false,
"ok": true,
"outcome": 3450.0,
"multiplier": 138.0,
"currency": "BRL"
},
{
"user_id": "usr_s1",
"seat_ref": "usr_s1",
"is_bot": true,
"ok": true,
"outcome": 210.5,
"multiplier": 8.42,
"currency": "BRL"
}
]
}
| Campo | Notas |
|---|---|
status | completed, failed ou cancelled |
reason | Presente em failed e cancelled. Por que terminou assim |
seats | Ausente em cancelled, porque nada foi gravado |
user_id / seat_ref | A referência do assento, usr_s<índice>. Os dois carregam o mesmo valor |
ok | Se este assento teve sucesso. Uma batalha completed pode conter assento falho |
multiplier | O multiplicador cru do assento |
status: "completed" não significa que todo assento teve sucessoSignifica que a batalha terminou. Cheque ok por assento. Um assento cuja gravação falhou volta
com ok: false dentro de uma batalha por outro lado completa, e liquidá-lo como vitória é um
bug de dinheiro real.
Verifique a assinatura
X-Slotbattle-Sign é o HMAC-SHA256 em hexadecimal do corpo cru da requisição, com a chave do
seu segredo de webhook.
Verifique sobre os bytes que você recebeu, antes de fazer parse. Reserializar o JSON e fazer hash disso não vai bater, porque ordem de chaves e formatação de número mudam.
import { createHmac, timingSafeEqual } from 'node:crypto'
function verify(rawBody, header, secret) {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
const a = Buffer.from(expected, 'utf8')
const b = Buffer.from(header ?? '', 'utf8')
return a.length === b.length && timingSafeEqual(a, b)
}
Use comparação de tempo constante. === numa assinatura a vaza byte a byte.
$expected = hash_hmac('sha256', $rawBody, $secret);
if (! hash_equals($expected, $request->header('X-Slotbattle-Sign', ''))) {
abort(401);
}
Recuse o que falhar. Uma chamada sem assinatura ou com assinatura errada não é uma chamada do SlotBattle.
A entrega é durável e idempotente
As entregas têm chave battle_id : status : version, então um evento terminal duplicado nunca
produz um segundo POST.
- 3 tentativas, e então a entrega é persistida como falha.
- Um reconciliador retenta entregas falhas com backoff de 30 segundos.
- A entrega é por tenant: um casino sem endpoint configurado é pulado, nunca redirecionado para o webhook da plataforma.
O seu endpoint ainda precisa ser idempotente. Timeouts de rede significam que você pode receber
uma entrega já processada, já que a garantia é ao menos uma vez e não exatamente uma vez. Use
battle_id + status como chave do seu processamento.
Responda rápido
Responda 2xx assim que tiver aceitado o payload de forma durável, e faça a liquidação de
forma assíncrona.
Um endpoint lento queima o orçamento de tentativas e empurra a entrega para o reconciliador, o que atrasa o resultado para todo mundo naquela batalha. Escreva numa fila e retorne.
Qualquer coisa fora de 2xx é falha e vai ser retentada.
Configurando o endpoint
O webhook é por casino, na categoria de settings webhook: no console,
Configurações → Webhook. Defina a URL e o segredo juntos.
Definir a URL e deixar o segredo em branco é recusado na escrita. Meio configurado, ele mandaria os seus resultados assinados com o segredo da plataforma.
O segredo também nunca volta. Ler as configurações responde se ele está definido, não qual é, e enviar o formulário sem ele deixa o armazenado intacto, então apertar Salvar não consegue limpá-lo em silêncio.
Testando sem endpoint público
O endpoint precisa ser alcançável a partir da instância. Durante o desenvolvimento, um túnel
como ngrok ou cloudflared apontado para o seu servidor local é a abordagem usual.
O tutorial constrói um receptor que verifica assinaturas e mostra como exercitá-lo.
Por que o socket não substitui
O WebSocket é melhor esforço e não tem replay. Um espectador, ou o seu próprio listener, pode
perder o battle.completed por ficar dois segundos desconectado, e nada vai reentregar. O
webhook retenta, é idempotente e é assinado.
Use o socket para manter a UI viva, e o webhook para mover dinheiro.
Próximo
- Eventos: a diferença entre os três consumidores
- WebSocket: a metade ao vivo
- Configurações no console