AVIADOR · GoDdsPlayCrash provably fair · API v1

GoDdsPlay RGS — Documentação da API

Servidor autoritativo (RGS) de um crash game provably fair com RTP de 97%. O servidor decide tudo — resultado da rodada, saques e auto-saque — o cliente apenas anima a curva. Não existe modo de forçar resultado em produção.

URL base da API http://127.0.0.1:7777 WebSocket: ws://127.0.0.1:7777/jogar/crash-test

·Visão Geral

ℹ️
Modelo de autenticação
· Jogador — token assinado (HMAC-SHA256) obtido em POST /api/session (demo anônima) ou POST /api/launch (cliente de operador). Formato <player_id>.<assinatura>, sem expiração (vale enquanto o AUTH_SECRET do servidor não mudar). Vai em ?token= no WebSocket.
· Admin — header Authorization: Bearer <ADMIN_TOKEN> ou x-admin-token: <ADMIN_TOKEN>. O token é impresso no console ao iniciar o servidor (ou definido em ADMIN_TOKEN).

Fórmula provably fair (commit-reveal)

fórmula
hash     = SHA256(`${serverSeed}-${clientSeed}-${nonce}`)
baseInt  = int(hash[0..8], 16)          // 32 bits
X        = baseInt / 2^32               // uniforme em [0, 1)

baseInt % 50 == 0  ->  crash = 1.00x    // instant crash (2%)
senão              ->  crash = floor((100-edge)/(100-2) * 1/(1-X) * 100) / 100
                       mínimo 1.00x · teto max_multiplier (padrão 10.000x)

Loop da rodada e timings (padrão)

FaseDuraçãoDescrição
BETTING5 sJanela de apostas (commit publicado)
RUNNINGvariávelCurva m(t) = e^(0,00006·t) truncada em 2 casas; tick a cada 100 ms
CRASHED3 s (cooldown)Seed revelada, apostas liquidadas, depois nova rodada

·Sessão do Jogador

POST/api/sessionEmitir ou reaproveitar token de jogador (demo anônima)

Se o token enviado for válido, devolve o mesmo player_id; caso contrário emite um novo. A carteira demo começa com 10.000 BRL (saldo em memória).

body
{ "token": "usr_05efb035f603.YNL7HbmX8klNiMYMlOM_IzFfbHcRtGAm" }
200 ok
{
  "player_id": "usr_05efb035f603",
  "token":     "usr_05efb035f603.YNL7HbmX8klNiMYMlOM_IzFfbHcRtGAm",
  "ws_path":   "/jogar/crash-test"
}
POST/api/launchLançar jogo para cliente de operador (integração)

Valida agente + cliente e emite o token interno do WebSocket para o jogador do operador, com o saldo real do cliente no RGS. Parâmetros no body ou na query string.

body
{
  "agent":   "agente-demo-749a0a",      // chave do agente (obrigatório)
  "user":    "jogador1",                // user_ref do cliente (obrigatório)
  "token":   "sess_1791909745_f641…",   // session_token do cliente (obrigatório)
  "operator": "real", "currency": "BRL", "lang": "pt"
}
200 ok
{
  "player_id": "cli_cf7639bafc2e",
  "token":     "cli_cf7639bafc2e.CJuLqJdryZpMhy_tucB3vEgVBNP-K8nw",
  "ws_path":   "/jogar/crash-test",
  "balance":   50, "currency": "BRL", "operator": "real",
  "client": { "id": "461b4d00-…", "user": "jogador1", "name": "J1" },
  "agent":  { "key": "agente-demo-749a0a", "name": "Agente Demo" },
  "min_bet": 1, "max_bet": 10000
}
ErroQuando
404agente inválido/inativo ou cliente não encontrado
400user ausente
403cliente inativo
401session_token do cliente inválido

·WebSocket do Jogo

WS/jogar/crash-test?token=<token>Conexão, handshake e limites
conexão
const ws = new WebSocket(`ws://${location.host}/jogar/crash-test?token=${encodeURIComponent(token)}`);
  • 401 na abertura se o token for inválido/ausente (ou caminho errado).
  • Rate limit: máx. 20 mensagens/s por conexão → ERROR RATE_LIMIT.
  • Heartbeat: ping/pong a cada 30 s (o RTT vira latency_ms nos logs).
  • Frame máximo: 4 KB por mensagem.
  • Na conexão o servidor envia SESSION (estado completo: rodada atual, histórico, saldo, apostas próprias).
⚠️
Desconexão — CONNECTED → GRACE_PERIOD (GRACE_MS, padrão 5 s) → DISCONNECTED. Políticas: EMERGENCY_CASH_OUT (padrão: saca a aposta em voo no multiplicador atual) ou LOST_ON_CRASH (aposta aberta perde no crash). Auto-saque configurado sempre continua valendo no servidor.

Cliente → Servidor

PLACE_BET{ action, slot, amount, currency, is_auto_cashout, auto_cashout_target }Apostar na janela de apostas

Só é aceito na fase BETTING. Uma aposta por slot por rodada. O débito é imediato e síncrono.

envio
{
  "action": "PLACE_BET",
  "slot": "a",                          // "a" | "b" | "c" (slots ativos: 1–3, padrão a+b)
  "amount": 10,                         // BRL, 2 casas, dentro dos limites
  "currency": "BRL",                    // apenas BRL
  "is_auto_cashout": true,              // opcional
  "auto_cashout_target": 1.5            // obrigatório se is_auto_cashout; 1.01–max_multiplier
}
resposta
{
  "event": "BET_ACCEPTED", "status": "SUCCESS", "timestamp": 1760640000000,
  "round_id": "48310806-fb34-…", "bet_id": "3413956f-…",
  "slot": "a", "amount": 10,
  "is_auto_cashout": true, "auto_cashout_target": 1.5,
  "balance": 9990
}
CASH_OUT{ action, slot, multiplier_claimed }Sacar (multiplicador autoritativo do servidor)

Só é aceito na fase RUNNING. O multiplier_claimed é ignorado — vale o multiplicador do servidor no instante do recebimento (multiplicador validado, 2 casas).

envio
{ "action": "CASH_OUT", "slot": "a", "multiplier_claimed": 500 }
resposta
{
  "event": "CASH_OUT_SUCCESS",
  "round_id": "48310806-…", "multiplier_validated": 1.33, "payout_credited": 13.3,
  "timestamp_settled": 1760640000000, "slot": "a", "bet_id": "3413956f-…",
  "settlement": "CASH_OUT_SUCCESS", "wallet_status": "CREDITED", "balance": 10003.3
}
💡
wallet_status pode ser CREDITED (creditado na hora) ou QUEUED (carteira instável — o crédito entra na fila de emergência e chega depois em WALLET_CREDITED, garantindo idempotência por bet_id).
PING{ action, timestamp }Keep-alive / medição de latência
envio
{ "action": "PING", "timestamp": 123 }
resposta
{ "event": "PONG", "timestamp": 1760640000000, "client_timestamp": 123 }

Servidor → Cliente (eventos)

SESSIONestado completo na conexão
exemplo
{
  "event": "SESSION", "player_id": "usr_3d0b17250c44", "balance": 10000, "currency": "BRL",
  "house_edge": 3, "rtp": 97, "min_bet": 1, "max_bet": 10000, "max_multiplier": 10000,
  "phase": "RUNNING",
  "round": { "round_id": "…", "commit": "…", "client_seed": "…", "nonce": 1554,
             "multiplier": 1.27, "ends_in_ms": 0 },
  "bots":   [{ "name": "Jo***10", "valor": 500, "cashed_at": null }],
  "history":[{ "id": "…", "nonce": 1548, "commit": "…", "serverSeed": "…",
              "clientSeed": "…", "crashPoint": 2.96, "rngRaw": 0.4705, "ts": "…" }],
  "flow":   [{ "id": "…", "apostas": 15, "ganhos": 19.5, "caixa": 99995.5, "crash": 1.69 }],
  "totals": { "rodadas": 6333, "apostado": 13260, "pago": 3719.4, "caixa": 19540.6 },
  "bots_config": { "min": 50, "max": 50 },
  "my_bets": { "a": { "amount": 10, "cashed_at": null, "payout": 0, "settled": false } }
}

my_bets permite reconectar no meio da rodada e restaurar apostas em andamento.

ROUND_BETTINGabre a janela de apostas (commit publicado)
exemplo
{
  "event": "ROUND_BETTING", "round_id": "…", "commit": "ecd8cf4548e6744a…",
  "client_seed": "0182672b…", "nonce": 1555, "ends_in_ms": 5000,
  "house_edge": 3, "timestamp": 1760640000000,
  "forced": false, "compensated": false,
  "bots": [{ "name": "Jo***10", "valor": 500 }]
}
ROUND_STARTEDdecolagem — começa a curva
exemplo
{ "event": "ROUND_STARTED", "round_id": "…", "timestamp": 1760640005000 }
GAME_TICKmultiplicador atual (100 ms) + saques de bots
exemplo
{
  "event": "GAME_TICK", "round_id": "…", "multiplier": 1.33, "timestamp": 1760640005200,
  "cashouts": [{ "i": 3, "name": "Ca***13", "multiplier": 1.3, "payout": 650 }]
}
GAME_CRASHEDfim da rodada — revela o server_seed
exemplo
{
  "event": "GAME_CRASHED", "round_id": "…", "final_multiplier": 7.24,
  "timestamp_crash": 1760640013000,
  "server_seed": "c921b203f06950b8…(64 hex)", "commit": "8ef48cc9009c71f8…",
  "client_seed": "0182672b…", "nonce": 1556,
  "totals": { "apostado": 30, "pago": 19.5, "caixa": 99989.5 },
  "bots": { "apostado": 25000, "pago": 9000, "ganhos": 18, "total": 50 },
  "late_cashouts": [], "cooldown_ms": 3000
}
💡
É aqui que a rodada se torna verificável: guarde server_seed + commit + client_seed + nonce + final_multiplier e confira em POST /api/verify.
BETS_SETTLEDliquidação por jogador (privado)
exemplo
{
  "event": "BETS_SETTLED", "round_id": "…", "final_multiplier": 1.86, "balance": 10004.5,
  "bets": {
    "a": { "bet_id": "…", "amount": 10, "cashed_at": 1.3, "payout": 13,   "result": "CASH_OUT_SUCCESS" },
    "b": { "bet_id": "…", "amount": 5,  "cashed_at": null, "payout": 0,   "result": "LOST_ON_CRASH" }
  }
}
WALLET_CREDITEDcrédito diferido (fila de failover ou admin)
exemplo
{
  "event": "WALLET_CREDITED", "bet_id": "…", "amount": 11, "balance": 10001,
  "source": "admin"        // presente quando o crédito veio do painel admin
}
CONFIGLIMITS_CONFIG · BOTS_CONFIG · COMPENSATION_CONFIG · BANKROLL_CONFIGmudanças de configuração propagadas para todos
exemplos
{ "event": "LIMITS_CONFIG", "min_bet": 2, "max_bet": 200, "max_multiplier": 300 }
{ "event": "BOTS_CONFIG", "min": 5, "max": 7 }
{ "event": "COMPENSATION_CONFIG", "enabled": false, "cycle": 1000 }
{ "event": "BANKROLL_CONFIG", "house_bankroll": 12345 }

Códigos de erro do WebSocket

CódigoAçãoMotivo
BETTING_CLOSEDPLACE_BETjanela de apostas fechada
INVALID_SLOTPLACE_BET / CASH_OUTslot inexistente ou não ativo
DUPLICATE_BETPLACE_BETaposta já feita neste slot/rodada
INVALID_AMOUNTPLACE_BETvalor inválido, >2 casas ou fora dos limites
INVALID_CURRENCYPLACE_BETmoeda não suportada (apenas BRL)
INVALID_TARGETPLACE_BETalvo de auto-saque inválido (1.01–max_multiplier, 2 casas)
WALLET_DEBIT_FAILEDPLACE_BETsaldo insuficiente
NOT_RUNNINGCASH_OUTsaque fora da fase RUNNING
NO_ACTIVE_BETCASH_OUTsem aposta ativa no slot
TOO_LATECASH_OUTservidor já passou do crash
UNKNOWN_ACTIONqualqueração desconhecida
RATE_LIMITqualquer>20 mensagens/s

·Verificação Provably Fair

POST/api/verifyQualquer um recalcula a rodada
body
{
  "serverSeed":  "33d74f821af3c4bd…(64 hex)",   // revelado no GAME_CRASHED
  "commit":      "883f8775d882b9b4…(64 hex)",   // publicado no ROUND_BETTING
  "clientSeed":  "0182672b7eb805c4…",
  "nonce":       2571,
  "crashPoint":  1.15
}
200 ok · válida
{
  "valido": true, "commitOk": true, "crashOk": true,
  "crashRecalculado": 1.33, "rngRaw": 0.2566413190215826
}
200 ok · rodada compensada
{
  "valido": false, "commitOk": true, "crashOk": false,
  "crashRecalculado": 1.16, "rngRaw": 0.14950600010342896,
  "sorteio_deslocado": 0.1466156188,
  "motivo": "rodada gerada com compensação de RTP ativa — sorteio deslocado da fórmula pública (a seed não foi adulterada)"
}
⚠️
Compensação de RTP quebra o provably fair. Rodadas geradas com compensação ativa (ou forçadas em teste) não batem na fórmula pública — o verificador informa o motivo. Em produção regulada, mantenha a compensação desligada.

·HTTP Público

GET/healthEstado do servidor (fase, banco, fila)
200 ok
{
  "status": "ok", "db": "sqlite", "queue": "memory", "retry_queue_length": 0,
  "phase": "RUNNING", "round_id": "67c1f64a-…", "multiplier": 1.11,
  "ws_path": "/jogar/crash-test", "house_edge": 3
}
GET/api/historyÚltimas 50 rodadas (com seed revelada)
200 ok
{
  "history": [{
    "id": "c45330d8-…", "nonce": 1554, "commit": "6baa76f5…", "serverSeed": "…",
    "clientSeed": "…", "crashPoint": 2.96, "rngRaw": 0.4705, "ts": "2026-10-13T17:08:04.713Z"
  }]
}
GET/api/rounds?limit=100Rodadas persistidas (limit 1–5000)
200 ok
{
  "count": 100,
  "rounds": [{
    "round_id": "…", "server_seed": "…", "client_seed": "…", "nonce": 1554,
    "rng_raw_result": 0.4705288903, "crash_multiplier": 2.96,
    "house_edge_percentage": 3,
    "timestamp_started": "…", "timestamp_crashed": "…"
  }]
}
GET/api/statsRTP medido (só jogadores reais; bots não contam)
200 ok
{
  "rodadas": 6331, "totalApostado": 13260, "totalPago": 3719.4,
  "rtpMedido": 28.05, "rtpTeorico": 97, "crashMedio": 9.42, "caixa": 19540.6
}
GET/api/themeTema visual atual do painel

Devolve o JSON completo do tema (cores, imagens, textos, layout, faixas de cor do multiplicador, opções).

·Admin

⚠️
Autenticação necessária em todos os endpoints abaixo: header Authorization: Bearer <ADMIN_TOKEN> ou x-admin-token: <ADMIN_TOKEN>. Sem/come token inválido → 401.
GET/admin/api/configConfiguração geral
200 ok
{
  "house_edge": 3,
  "bots": { "min": 50, "max": 50 },
  "policy": { "graceMs": 5000, "disconnect": "EMERGENCY_CASH_OUT" },
  "db": "sqlite", "queue": "memory", "wallet_down": false,
  "compensation": { "enabled": false, "cycle": 1000, "count": 0, "rounds_left": 1000, "debt": 0, "mean_x": 0.5, "rtp": { "2": null, "5": null, "10": null, "1.5": null }, "forced_next": null },
  "limits": { "min_bet": 1, "max_bet": 10000, "max_multiplier": 10000 },
  "house_bankroll": 100000
}
GET/admin/api/tokenDevolve o admin_token (para o operador logado)
200 ok
{ "admin_token": "zKBLS0cf8Y3g…" }
GET/admin/api/bets?player_id=&limit=100Logs de apostas (limite 1–5000)
200 ok
{
  "logs": [{
    "bet_id": "…", "round_id": "…", "player_id": "usr_…",
    "bet_amount": 5, "currency_code": "BRL", "is_auto_cashout": false, "auto_cashout_target": null,
    "connection_status_at_action": "CONNECTED", "action_type": "CASH_OUT_SUCCESS",
    "multiplier_achieved": 1.3, "payout_amount": 6.5,
    "player_ip_address": "127.0.0.1", "latency_ms": 12,
    "timestamp_action": "…", "parent_bet_id": "…"      // liga CASH_OUT/LOST ao BET_PLACED
  }]
}
POST/admin/api/botsFaixa de bots visuais { min, max }
body
{ "min": 50, "max": 120 }
200 ok
{ "ok": true, "min": 50, "max": 120 }

Bots são apenas simulação visual — não entram nas métricas nem no banco.

GET/admin/api/flow?limit=200Fluxo por rodada (apostas, ganhos, caixa, RTP, bots)
200 ok
{
  "totals": { "rodadas": 6333, "crashMedio": 4.46, "apostado": 15, "pago": 19.5 },
  "flow": [{
    "round_id": "…", "crash_multiplier": 1.69, "apostas": 15, "ganhos": 19.5,
    "caixa": 99995.5, "rtp": 130,
    "bots_apostado": 12625, "bots_pago": 4466.17, "bots_rtp": 35.4,
    "bots_ganhos": 11, "bots_total": 50
  }],
  "botTotals": { "total_apostado": 63125, "total_pago": 22330.85, "rodadas": 5, "rtp_medio": 35.4 },
  "house_bankroll": 12345
}
GET/admin/api/queueFila de failover e dead-letter
200 ok
{ "retry": [], "dead_letter": [] }
POST/admin/api/walletSimular queda/retorno da carteira (teste de failover)
body
{ "down": true }
200 ok
{ "ok": true, "down": true }

Com a carteira "fora", créditos vão para a fila de emergência e o jogador recebe wallet_status: "QUEUED"; quando volta, o worker credita e envia WALLET_CREDITED.

GETPOST/admin/api/limitsLimites de jogo
body
{ "min_bet": 2, "max_bet": 200, "max_multiplier": 300 }
200 ok
{ "ok": true, "min_bet": 2, "max_bet": 200, "max_multiplier": 300 }

Vale já para as próximas apostas; propagado em LIMITS_CONFIG. Se max_bet < min_bet, o máximo é corrigido para o mínimo.

GETPOST/admin/api/bankrollBanca da casa (reserva exibida no caixa)
body
{ "house_bankroll": 250000.5 }
200 ok
{ "ok": true, "house_bankroll": 250000.5 }

Não altera resultados — apenas mantém o caixa exibido não-negativo.

GETPOST/admin/api/compensationCompensação de RTP no ciclo
body
{ "enabled": true, "cycle": 1000 }
⚠️
Quebra o provably fair: rodadas compensadas não passam em /api/verify (o verificador informa o motivo). Use apenas em teste/demonstração — em produção regulada mantenha desligado. Todo uso fica registrado no log de auditoria do admin.
POST/admin/api/forceForçar multiplicador da próxima rodada
body
{ "crash": 50.25 }      // ou { "clear": true }
⚠️
Recurso de demonstração: a rodada forçada fica marcada (forced: true no ROUND_BETTING) e não verifica no provably fair. Auditoria registra quem forçou.
GETPOST/admin/api/theme · /admin/api/theme/assetAparência do painel + upload de imagens
body (tema)
{
  "colors":   { "green": "#00ff00" },
  "images":   { "logo": "https://… ou data:image/… ou /theme-assets/…" },
  "texts":    { "brand": "Minha Marca" },
  "layout":   { "feedW": 270, "headerH": 40, "gap": 6, "stageMaxW": 0 },
  "multColors": [{ "min": 1, "max": 2, "color": "#913ef8" }, { "min": 2, "max": null, "color": "#34b4ff" }],
  "options":  { "showAdmin": true, "betButtons": 2, "showSpinner": true },
  "rawCss":   ".stage{border-radius:24px}",
  "reset":    false
}
body (asset)
{ "mime": "image/png", "data": "<base64>" }     // png/jpg/gif/webp/svg/avif, até 4MB
→ { "ok": true, "url": "/theme-assets/a1b2c3d4….png" }

Tudo é sanitizado: cores hex, imagens com esquema seguro, CSS sem <. betButtons clampa 1–3.

Agentes (operadores)

GETPOST/admin/api/agentsListar / criar operador
body (criar)
{
  "name": "Meu Cassino",                      // obrigatório
  "key": "meu-cassino",                       // opcional (gera slug se omitir)
  "webhook_url": "https://…",                 // opcional, armazenado para referência
  "token": "…", "secret": "…",                // credenciais externas, armazenadas
  "currency": "BRL", "active": true
}
201 criado
{
  "ok": true,
  "agent": {
    "id": "7f7238e7-…", "key": "meu-cassino", "name": "Meu Cassino",
    "webhook_url": null, "token": null, "secret": null,
    "currency": "BRL", "active": true,
    "link_template": "http://127.0.0.1:7777/?agent=meu-cassino&user={user}&token={token}&operator=real¤cy=BRL&lang=pt"
  }
}
ℹ️
O link_template usa os marcadores {user} e {token} — o seu sistema substitui pelo user_ref e session_token do jogador. No modo padrão (wallet_mode: "rgs") o webhook_url é apenas armazenado e o saldo vive no RGS (ver Integração). No modo seamless ele recebe os callbacks de carteira (ver Seamless).
PUTDELETE/admin/api/agents/:idEditar / excluir operador
body (editar)
{ "name": "Novo Nome", "active": false, "currency": "BRL" }

Excluir um agente remove também todos os seus clientes. 409 se a nova key já existir.

Clientes (jogadores de operador)

GETPOST/admin/api/clientsListar (por agente) / criar cliente
query
GET /admin/api/clients?agent_id=7f7238e7-…
body (criar)
{
  "agent_id": "7f7238e7-…",            // obrigatório
  "user_ref": "jogador1",              // ID único do jogador no sistema do operador
  "name": "Jogador 1",
  "session_token": "sess_…",           // opcional (gera um se omitir) — usado no link/launch
  "initial_balance": 50,               // BRL
  "currency": "BRL", "active": true
}
201 criado
{
  "ok": true,
  "client": {
    "id": "461b4d00-…", "pid": "cli_cf7639bafc2e", "agent_id": "7f7238e7-…",
    "user_ref": "jogador1", "name": "J1", "session_token": "sess_1791…",
    "balance_cents": 5000, "currency": "BRL", "active": true,
    "link": "http://127.0.0.1:7777/?agent=meu-cassino&user=jogador1&token=sess_1791…&operator=real¤cy=BRL&lang=pt"
  }
}

O link pronto abre o painel já logado no jogador. 409 se o user_ref já existir no agente.

PUTDELETE/admin/api/clients/:idEditar / excluir cliente
body (editar)
{ "user_ref": "novo", "name": "…", "session_token": "…", "currency": "BRL", "active": true }
POST/admin/api/clients/:id/balanceCreditar / definir saldo (com lançamento auditável)
body
{ "add": 8, "note": "bônus boas-vindas" }     // ou { "set": 100 }
200 ok
{ "ok": true, "client_id": "461b4d00-…", "balance": 58 }

Se o jogador estiver conectado, recebe WALLET_CREDITED na hora (com source: "admin").

GET/admin/api/clients/:id/tx?limit=100Extrato do cliente (wallet_tx)
200 ok
{
  "tx": [{
    "id": "c201cf7f-…", "client_id": "461b4d00-…", "kind": "admin_credit",
    "amount_cents": 800, "balance_after_cents": 5800,
    "ref": null, "note": null, "created_at": "2026-10-13T16:42:25.478Z"
  }]
}

kind possível: bet, win, admin_credit, admin_set. ref traz a chave de idempotência (ex.: credit:<bet_id>).

·Integração do Operador (fluxo)

  1. Crie o agente (admin) → guarde a key e o link_template.
  2. Crie o cliente (admin) com o user_ref do SEU sistema, um session_token gerado por você e o saldo inicial → recebe o link pronto do jogador.
  3. Entregue o link ao jogador (iframe, redirect, botão…). Ao abrir, o painel chama POST /api/launch sozinho, valida o session_token e conecta no WebSocket com o saldo do RGS.
  4. Acompanhe pelo extrato (/admin/api/clients/:id/tx) e pelos logs (/admin/api/bets?player_id=cli_…). Créditos/débitos por rodada ficam no RGS com idempotência por aposta.
ℹ️
Diferenças em relação a integrações com webhook: este RGS mantém o saldo do jogador dentro dele (tabela wallet_tx + lançamentos ligados por parent_bet_id) e não dispara callbacks para o operador. O campo webhook_url do agente fica armazenado para referência/contato. Se precisar espelhar saldos no seu sistema, use os endpoints de extrato e logs.
💡
Recarga em produção: use POST /admin/api/clients/:id/balance com add — é idempotente por lançamento, notifica o jogador online e cai no extrato com note para conciliação.

·Carteira do Operador (seamless)

Agente criado com "wallet_mode": "seamless" (exige webhook_url e secret): o saldo do jogador fica na plataforma do operador. Cada aposta e cada prêmio viram uma chamada assinada para o webhook_url; o RGS só guarda uma cópia do último saldo informado e o lançamento em wallet_tx para auditoria. Ajuste manual de saldo no RGS é recusado (409) para esses clientes.

🔐
Assinatura (nos dois sentidos): headers X-GoDds-Agent: <key> e X-GoDds-Signature: sha256=<hex HMAC-SHA256(secret, corpo bruto)>. O corpo JSON leva ts (epoch ms); diferença maior que 5 min é recusada.
POST/api/operator/sessionOperador cria/atualiza o jogador e recebe o link

Cria o cliente na primeira vez (por user) e gera um session_token novo a cada chamada (o anterior deixa de valer).

body (assinado)
{ "user": "7", "name": "Jogador", "currency": "BRL", "ts": 1792170000000 }
200 ok
{
  "ok": true, "player_id": "cli_3f9a…", "wallet_mode": "seamless",
  "url":   "https://jogo…/?agent=godds-club&user=7&token=sess_…&operator=real&currency=BRL&lang=pt",
  "query": "agent=godds-club&user=7&token=sess_…&operator=real&currency=BRL&lang=pt",
  "session_token": "sess_…"
}

Erros: 404 INVALID_AGENT, 401 INVALID_SIGNATURE, 400 USER_REQUIRED, 403 CLIENT_INACTIVE.

POST<webhook_url do agente>Callbacks do RGS para a carteira do operador
ações
{ "action": "balance",  "user": "7", "game": "aviador", "ts": … }
{ "action": "debit",    "user": "7", "amount": 10, "txn_id": "debit:<bet_id>",  "round_id": "…", "bet_id": "…", "game": "aviador", "ts": … }
{ "action": "credit",   "user": "7", "amount": 15, "txn_id": "credit:<bet_id>", "round_id": "…", "bet_id": "…", "multiplier": 1.5, "game": "aviador", "ts": … }
{ "action": "rollback", "user": "7", "amount": 10, "txn_id": "rollback:<bet_id>", "ref_txn_id": "debit:<bet_id>", "round_id": "…", "bet_id": "…", "game": "aviador", "ts": … }
resposta
{ "ok": true, "balance": 105.00 }
{ "ok": false, "error": "INSUFFICIENT_FUNDS", "balance": 4.20 }
{ "ok": false, "error": "BET_REFUSED", "message": "limite de jogo responsável" }
  • txn_id é a chave de idempotência: a mesma chamada repetida deve devolver o saldo sem lançar de novo.
  • debit: a aposta só é aceita depois do ok. Recusa vira WALLET_DEBIT_FAILED ou BET_REFUSED no WebSocket.
  • credit com falha de rede entra na fila de failover e é reenviado (mesmo txn_id) até confirmar.
  • rollback: se o débito for aprovado depois que a janela de apostas fechou, o valor é devolvido e a aposta recusada.
  • Aposta perdida não gera chamada (o débito já foi feito).
⚠️
Forçar resultado e compensação de RTP alteram o sorteio e quebram o provably fair. Ficam desligados no servidor e só respondem com ALLOW_RESULT_CONTROL=1 (uso em teste). Sem a variável, uma compensação que estava ligada é desligada ao iniciar.

·Compatibilidade Legado (SSE + REST)

ℹ️
Jogador legado é único e anônimo (sem token), como na versão anterior. Nada foi removido.
GET/legacy · /legacy.htmlPainel antigo

Serve legacy/panel.legacy.html.

SSE/streamEventos: hello · betting · start · tick · crash · cashout · botsconfig · config · forced
exemplo (crash)
event: crash
data: {
  "roundId": "…", "crashPoint": 2.96, "serverSeed": "…", "commit": "…",
  "clientSeed": "…", "nonce": 1554, "wallet": 10004.5,
  "apostas": { "a": { "valor": 10, "cashedAt": 1.3, "pagamento": 13 }, "b": null },
  "totalApostado": 30, "totalPago": 19.5, "deltaCaixa": 10.5, "caixa": 99990.5,
  "botsGanhos": 18, "botsTotal": 50, "forced": false, "cooldownMs": 3000
}
POST/api/bet · /api/cashoutAposta e saque do jogador legado
body
{ "slot": "a", "valor": 10 }        // /api/bet
{ "slot": "a" }                       // /api/cashout
200 ok
{ "ok": true, "slot": "a", "wallet": 9990, "bet": 10, "bet_id": "…" }
{ "ok": true, "slot": "a", "cashoutEm": 1.3, "pagamento": 13, "wallet": 10003 }

Erros voltam como { "erro": "…" } com HTTP 200 (mesma semântica da versão antiga).

POST/api/bots · /api/config · /api/forceAdministração legada
  • POST /api/bots {min, max} — exige admin token.
  • POST /api/config {edge} — exige admin token; 403 se HOUSE_EDGE estiver definido no ambiente (trava de compliance).
  • POST /api/force {crash} — só existe com NODE_ENV=test; em produção cai no 404.

·Variáveis de Ambiente

VariávelPadrãoDescrição
PORT / HOST7777 / 127.0.0.1Endereço HTTP + WS
DATABASE_URL—PostgreSQL; sem ela usa SQLite em data/godds_crash.db
REDIS_URL—Fila de failover no Redis; sem ela, em memória
AUTH_SECRET / ADMIN_TOKENgeradosSegredos — gerados e salvos no banco se não definidos
HOUSE_EDGE3Edge em % (RTP = 100 − edge); definido no env trava alteração em runtime
DISCONNECT_POLICYEMERGENCY_CASH_OUTou LOST_ON_CRASH
GRACE_MS5000Tempo em GRACE_PERIOD antes de DISCONNECTED
WALLET_FAILURE_RATE00–1 — simula carteira instável (teste de failover)
ALLOW_RESULT_CONTROL01 libera forçar resultado / compensação de RTP (só teste — quebra o provably fair)
PUBLIC_URLhost da requisiçãoURL pública do jogo usada nos links do jogador (ex.: quando o operador chama o RGS por um endereço interno)