Pular para o conteúdo

Referência da API de carteira

O dinheiro do jogador fica na sua carteira. A plataforma chama o seu servidor a cada movimentação: quatro endpoints POST sob a walletUrl que você nos informa (por exemplo https://wallet.seu-cassino.example/sportsbook).

Esses endpoints atendem consultas de saldo e movimentações de apostas. Eles não implementam depósitos, saques, PIX, cartões ou outros meios de pagamento do jogador. Isso permanece no caixa e nos provedores de pagamento da sua plataforma. Em particular, um credit de liquidação atualiza a carteira do cassino; não é um pagamento bancário.

Endpoints
balance · debit · credit · rollback
Assinatura
HMAC-SHA256, hex
Janela de relógio
±300 s
Timeout
5 s por tentativa

Este exemplo fixo usa o segredo público de teste documentation-only-secret. O timestamp antigo serve apenas à verificação offline; use um timestamp atual nas chamadas reais. Baixe o vetor de teste.

Toda chamada é um POST com corpo JSON e três cabeçalhos:

POST /sportsbook/debit HTTP/1.1
Content-Type: application/json
X-Timestamp: 1760000000
X-Signature: 5a409c37866145f0505662fc18747d92fb61c08c9bff78c1f404ad8779b9428d
{"txId":"bet:8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53","playerId":"player-42","amount":500,"currency":"USD","kind":"bet","betId":"8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53"}
Campo Tipo
txId string Chave de idempotência, até 200 caracteres. Veja os ids de transação.
playerId string O id do jogador no seu sistema: o externalPlayerId enviado ao criar a sessão.
amount inteiro Unidades menores (centavos). Sempre zero ou positivo; o endpoint dá a direção.
currency string A moeda do tenant, por exemplo USD, BRL.
kind string O tipo da movimentação, veja a tabela abaixo.
betId string A aposta, quando a movimentação pertence a uma.
ref string Contexto: o débito sendo revertido, ou o resultado que gerou um pagamento, por exemplo result:ev_8f2c/1x2@1.
kind Endpoint
balance /balance Consulta de saldo (amount é 0).
bet /debit Um valor apostado.
payout /credit A primeira liquidação de uma aposta: o prêmio, ou 0 para uma aposta perdida.
refund /credit Uma aposta anulada: o valor apostado volta.
cashout /credit O jogador fez cash-out.
resettle /credit ou /debit Uma correção depois de um mercado reliquidado.
rollback /rollback Um débito de aposta que não virou aposta.
adjustment /credit ou /debit Uma correção manual pela equipe da plataforma, auditada.
txId Chamada
balance:<uuid> /balance (novo a cada vez)
bet:<betId> /debit, o valor apostado
rollback:bet:<betId> /rollback desse valor (ref = bet:<betId>)
payout:<betId>:<version> /credit, primeira liquidação (kind payout ou refund)
resettle:<betId>:<version>:<n> /credit ou /debit, a n-ésima liquidação da aposta
cashout:<betId> /credit, cash-out

Trate os formatos como informativos: conte com o txId ser único por movimentação, não com o formato dele.

Responda 2xx com o saldo depois da movimentação:

{ "txId": "bet:8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53", "balance": 49500, "currency": "USD" }

O txId é obrigatório em débito, crédito e rollback e deve ser exatamente o da requisição. balance deve ser um inteiro explícito (zero é válido; ausente ou nulo não), e currency deve coincidir com a requisição. A consulta de saldo não precisa devolver txId. Para recusar, use 4xx com JSON e error.code não vazio:

{ "error": { "code": "insufficient_funds", "message": "insufficient funds" } }

Como lemos a sua resposta:

Sua resposta Significa O que fazemos
2xx, txId e moeda correspondentes, saldo inteiro explícito, sem error Aplicada. Pronto.
4xx, exceto 408 e 429, com envelope de erro válido Recusada, nada se moveu. Uma aposta: é recusada (insufficient_funds diz ao jogador o motivo). Qualquer outra coisa: sem retentativa, marcada para revisão manual.
408, 429, 5xx, timeout, erro de conexão, corpo de sucesso/erro incompleto ou inválido, transação ou moeda divergente Desconhecido: você pode ou não ter aplicado. Retentada ou revertida, veja Retentativas.
{"txId":"balance:5f0c7a52-3f6e-4f6e-9a54-0b8f8f6e2a11","playerId":"player-42","amount":0,"currency":"USD","kind":"balance"}

Responda {"balance": 50000, "currency": "USD"}. Chamado quando o sportsbook mostra o saldo; nenhum dinheiro se move. Se falhar, o sportsbook não mostra saldo até a próxima chamada bem-sucedida.

{"txId":"bet:8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53","playerId":"player-42","amount":500,"currency":"USD","kind":"bet","betId":"8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53"}
  • kind: "bet" é um valor apostado. Se o saldo não bastar, recuse com o código insufficient_funds (402 é o usual; qualquer 4xx exceto 408/429 funciona).
  • Um débito de aposta é enviado uma vez e nunca retentado. Se o resultado for desconhecido, nós o revertemos e recusamos a aposta, para que nenhuma aposta seja aceita com dinheiro que você não confirmou.
  • kind: "resettle" ou "adjustment" é uma correção devida pelo jogador. Aplique somente com saldo para o valor integral; caso contrário recuse com insufficient_funds para análise do operador. Veja correções.
{"txId":"payout:8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53:1","playerId":"player-42","amount":1050,"currency":"USD","kind":"payout","betId":"8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53","ref":"result:ev_8f2c/1x2@1"}

Créditos são devidos: retentamos até você confirmar (veja abaixo). Aceite amount: 0.

{"txId":"payout:d4e8a6b2-1c3f-4e5a-9b7d-6f0a2c4e8b19:1","playerId":"player-42","amount":0,"currency":"USD","kind":"payout","betId":"d4e8a6b2-1c3f-4e5a-9b7d-6f0a2c4e8b19","ref":"result:ev_8f2c/1x2@1"}

Toda aposta liquidada recebe exatamente um crédito payout:, inclusive quando perde. Use-o para encerrar a aposta nos seus registros (muitos reguladores exigem um resultado explícito por aposta). Responda 2xx com o saldo inalterado.

{"txId":"rollback:bet:f27b9c1e-6d4a-4b8f-83e2-5a1c9d7e0b46","playerId":"player-42","amount":200,"currency":"USD","kind":"rollback","betId":"f27b9c1e-6d4a-4b8f-83e2-5a1c9d7e0b46","ref":"bet:f27b9c1e-6d4a-4b8f-83e2-5a1c9d7e0b46"}

Reverte o débito de aposta cujo txId é o ref, porque a aposta não se concretizou (a sua resposta ao débito se perdeu, ou a aposta falhou depois dele).

  1. Se você aplicou esse débito e ainda não o reverteu: devolva o dinheiro e marque o débito como revertido.
  2. Se você já o reverteu: não faça nada e responda 2xx.
  3. Se você nunca recebeu esse débito (ele estourou o tempo antes de chegar, ou ainda está a caminho): responda 2xx com o saldo atual e guarde o ref. Se o débito chegar depois, recuse com um 4xx (rolled_back). Senão o jogador pagaria por uma aposta que não existe.

amount é o valor apostado original; devolva o que você de fato debitou.

X-Signature = hex( HMAC-SHA256( walletSecret, X-Timestamp + "." + rawBody ) )
  • rawBody é o corpo da requisição exatamente como recebido, byte a byte. Verifique antes de interpretar; nunca reserialize o JSON (a ordem das chaves e os espaços mudariam).
  • X-Timestamp é o horário Unix, em segundos, em que a tentativa foi enviada. Recuse requisições a mais de 300 segundos do seu relógio (proteção contra replay) e mantenha os servidores em NTP.
  • Compare em tempo constante. O hex vem em minúsculas; aceite os dois casos.
  • Uma retentativa traz timestamp e assinatura novos sobre o mesmo corpo.
walletSecret = dev-wallet-secret
X-Timestamp = 1760000000
rawBody = {"txId":"bet:8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53","playerId":"player-42","amount":500,"currency":"USD","kind":"bet","betId":"8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53"}
texto assinado = 1760000000.{"txId":"bet:8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53","playerId":"player-42","amount":500,"currency":"USD","kind":"bet","betId":"8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53"}
X-Signature = 5a409c37866145f0505662fc18747d92fb61c08c9bff78c1f404ad8779b9428d

Confira no terminal:

Janela do terminal
printf '%s' '1760000000.{"txId":"bet:8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53","playerId":"player-42","amount":500,"currency":"USD","kind":"bet","betId":"8c1d3f2a-5b7e-4c69-a0d4-2e9f6b1c7a53"}' \
| openssl dgst -sha256 -hmac dev-wallet-secret

Cada exemplo é testado contra requisições assinadas pelo próprio código do cliente de carteira da plataforma: requisições válidas passam; corpo alterado e timestamp de 301 segundos atrás falham.

samples/verify-signature.mjs
import { createHmac, timingSafeEqual } from 'node:crypto';
const MAX_SKEW_SECONDS = 300;
/**
* @param {string} secret your wallet secret
* @param {string} timestamp the X-Timestamp header (unix seconds)
* @param {string} signature the X-Signature header (hex)
* @param {Buffer|string} rawBody the request body exactly as received
* @returns {null | 'bad_timestamp' | 'bad_signature'}
*/
export function verifySignature(secret, timestamp, signature, rawBody, nowSeconds = Math.floor(Date.now() / 1000)) {
if (!/^\d+$/.test(timestamp ?? '') || Math.abs(nowSeconds - Number(timestamp)) > MAX_SKEW_SECONDS) return 'bad_timestamp';
const expected = createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest();
const given = Buffer.from(signature ?? '', 'hex');
return given.length === expected.length && timingSafeEqual(given, expected) ? null : 'bad_signature';
}

Em caso de falha, responda 401 com {"error": {"code": "invalid_signature"}}.

O txId é a chave de idempotência de toda chamada de dinheiro.

  • Guarde cada txId processado com o resultado (status e corpo).
  • Quando um txId chegar de novo, responda o resultado guardado e não mova dinheiro. É isso que torna as retentativas seguras.
  • Se um txId conhecido chegar com outro jogador, endpoint, valor, moeda, tipo, aposta ou referência, recuse (409, duplicate_tx).
  • Aplique a movimentação e guarde o txId na mesma transação do banco.
Timeout por tentativa 5 segundos
Débitos de aposta (kind: bet) Uma tentativa. Resultado desconhecido: revertido, aposta recusada.
Créditos, rollbacks, débitos de correção Até 5 tentativas, com esperas de 200 ms, 400 ms, 800 ms e 1,6 s entre elas (dobrando, no máximo 2 s)
Ainda desconhecido depois disso Fica pendente; operações elegíveis são reenviadas com o mesmo txId e corpo. Correções, recusas e dependências podem bloquear o reenvio; veja os limites operacionais abaixo.
Liquidações Também ficam em fila: uma liquidação que não pôde ser paga é retentada a cada 30 segundos

Assim, um crédito pode chegar minutos ou horas depois da liquidação (após uma indisponibilidade do seu lado), e mais de uma vez. A idempotência torna isso inofensivo.

Uma correção de resultado gera uma movimentação resettle: crédito quando há mais a pagar, débito quando houve pagamento a maior. O valor é a diferença em relação à liquidação anterior.

Valide saldo e aplique o débito na mesma transação de banco. Se faltar saldo para o valor integral, responda 402 com error.code: "insufficient_funds" e não mova dinheiro. Não cobre parcialmente, não crie saldo negativo e não capture depósitos futuros. Guarde a recusa no txId original; duplicatas recebem a mesma resposta mesmo após um depósito.

O OrbiSportbook mantém a obrigação em failed, interrompe o envio automático das movimentações seguintes dessa aposta e a exibe em Central do operador → Integração → Entrega financeira e exceções. O operador investiga com os IDs originais da transação e da aposta. Não gere outro ID para cobrar novamente nem marque o pagamento como concluído sem comprovação das duas carteiras.

Situação Status error.code O jogador vê
Aposta maior que o saldo 402 insufficient_funds “Saldo insuficiente”
Jogador desconhecido 404 player_not_found A aposta é recusada (payment_rejected)
Jogador bloqueado do seu lado 403 player_blocked A aposta é recusada (payment_rejected)
Assinatura inválida ou timestamp velho 401 invalid_signature A aposta é recusada
txId reutilizado com outros parâmetros 409 duplicate_tx A aposta é recusada
Débito já revertido 409 rolled_back A aposta é recusada
Falha temporária 5xx ou timeout qualquer Uma aposta: recusada como temporariamente indisponível. Demais: retentadas

Só insufficient_funds tem significado especial. Qualquer outro código fica registrado no log de chamadas de carteira da plataforma e volta nos detalhes do erro como operatorCode.

  • Mantenha uma linha de ledger por txId: endpoint, tipo, valor, aposta, status, horário.
  • A entrada em dinheiro usa bet:. Se não houver aceite, o rollback pode ainda estar pendente. A liquidação usa payout: (possivelmente zero), o cash-out integral usa cashout: e os parciais usam cashout:<betId>:p<n>. Correções usam resettle:. Consulte a aposta e as pendências; um débito isolado não comprova aposta aberta nem encerramento financeiro.
  • A plataforma registra toda chamada de dinheiro que faz a você (status, tentativas, a sua resposta) e mantém as não resolvidas pendentes até serem resolvidas.
  • Diariamente, compare os seus totais por dia UTC com os Relatórios do back office (GET /api/v1/admin/reports/ggr?from=AAAA-MM-DD&to=AAAA-MM-DD: apostas, prêmios, reembolsos, cash-outs e GGR), e consulte apostas individuais em Apostas (GET /api/v1/admin/bets?player=<id do jogador>).

As movimentações de uma aposta são enviadas na ordem persistida. Uma movimentação incerta ou recusada impede o envio das posteriores da mesma aposta; outras apostas continuam. Retentativas preservam a identidade financeira original. Após a queda de um worker, uma mesma transação pode chegar novamente: a idempotência atômica na carteira continua obrigatória. Não existe garantia de entrega única pela rede.

O resultado da aposta pode aparecer antes da confirmação da carteira. O estado won, cashed_out ou HTTP 200 de uma operação da aposta não comprova pagamento. Confira a fila financeira e o registro da carteira. Cash-outs parciais podem gerar vários créditos cashout:<betId>:p<n> antes da liquidação do saldo de aposta restante. Um débito isolado não comprova aposta aberta: pode haver um rollback pendente.

O transporte atual tem quatro endpoints. Consulta ao status de uma transação não está implementada nesta revisão. Resultados incertos são conciliados por reenvio idempotente ou rollback da entrada; preserve os registros para investigação. Não descarte as chaves de idempotência após uma janela curta.

Exceção: crédito corrigido e não confirmado fica para revisão. Se uma correção posterior tornar obsoleto um crédito positivo ainda incerto, o OrbiSportbook interrompe o envio automático e registra obsolete_credit_requires_review. Não presuma que o crédito ou a correção foram aplicados. Compare as duas carteiras com os IDs originais antes de resolver o incidente. Cash-outs já confirmados continuam garantidos. O conciliador genérico da carteira não pode ultrapassar a fila de liquidação, sua ordem ou seus bloqueios.

Baixe o contrato OpenAPI e siga os cenários de homologação.

A URL da carteira, o modo de carteira e a moeda ficam fixos após o primeiro uso financeiro. A troca exige migração controlada com a equipe de integração, incluindo saldos, histórico de idempotência, apostas abertas e correções posteriores; uma alteração comum retorna 409 com motivo wallet_identity_locked. A rotação coordenada do segredo de assinatura continua disponível.

Uma transação pendente sem prova do destino original fica retida para análise com o operador. Não a reenvie para uma URL substituta apenas porque ela está configurada agora.