Referência rápida do integrador¶
Uma página com as regras obrigatórias, os endpoints, os erros e os estados — no formato que um humano consulta com o conector aberto e que um agente de código usa como especificação. Tudo aqui foi conferido no código do Dracma em 2026-09-27; o detalhe está nas páginas linkadas e as diferenças para o contrato em divergencias.md.
1. Regras do integrador (MUST / NEVER)¶
Palavras no sentido da RFC 2119. Cada regra tem um id estável.
Chamadas ao Dracma¶
- R01 MUST chamar o Dracma só do servidor do conector. NEVER do navegador, do app do jogador ou de
código com segredo em bundle (
NEXT_PUBLIC_*,VITE_*). - R02 MUST assinar toda requisição: pedido canônico de 6 linhas sem
\nfinal, path com/v1e query exatamente como enviada, sha256 do corpo cru,HMAC-SHA256com o segredo da chave (autenticacao.md). - R03 MUST gerar nonce de 32 hex minúsculos por CSPRNG, novo em toda requisição, inclusive em retry.
NEVER
Math.random()/math/rand. - R04 MUST mandar
Dracma-Timestampem segundos e manter o relógio por NTP (desvio > 5 s = alerta). - R05 MUST serializar o corpo uma vez e enviar exatamente os bytes assinados.
- R06 MUST reproduzir os vetores de
exemplos/vetores-assinatura.jsonbyte a byte no CI, inclusive recusar o negativo. - R07 MUST usar timeout de cliente de 10 s.
Idempotência e repetição¶
- R08 MUST mandar
Idempotency-Keyem todoPOST(inclusivecancele/sandbox/*). - R09 MUST usar
Idempotency-Key = external_idemPOST /depositsePOST /withdrawals, e gravar os dois no banco antes da chamada (outbox). - R10 NEVER gerar
Idempotency-Keynova para repetir umPOSTde dinheiro. Repetição = mesma chave, mesmos bytes, timestamp/nonce/assinatura novos. - R11 MUST repetir
5xx,429,409 idempotency_in_progress, timeout e erro de rede com a mesma chave, respeitandoRetry-After(backoff exponencial com jitter). - R12 NEVER repetir um
4xx(é decisão). Tentar de novo depois de uma recusa = operação nova comexternal_idnovo. - R13 MUST tratar
codedesconhecido pela classe HTTP (4xx não repete; 5xx/429 repete).
Webhooks e fetch-back¶
- R14 MUST verificar a assinatura do webhook sobre o corpo cru, antes de interpretar o JSON, aceitando
qualquer
v1com qualquer segredo ativo (lista de 1 ou 2), em tempo constante; janela de 300 s;Dracma-Timestamp=t=. - R15 MUST responder
400seDracma-Event-Id≠iddo corpo e deduplicar porDracma-Event-Id(≥ 7 dias): duplicata →200sem efeito. - R16 MUST fazer fetch-back (
GET /deposits/{id}ouGET /withdrawals/{id}com a própria credencial) antes de mover dinheiro. NEVER creditar, debitar ou devolver reserva pelo corpo do webhook. - R17 MUST conferir no fetch-back:
external_idé seu;amount.amount,amount.currencyeplayer_refidênticos ao registrado. Divergência → não move dinheiro, revisão, alerta. - R18 MUST ignorar estado com
version≤ a última aplicada (responde200). - R19 MUST responder ao webhook em ≤ 10 s;
503quando o fetch-back falhar por rede/5xx (o Dracma reentrega). - R20 MUST rodar um job de
GET /events?after=<último evt aplicado>a cada 5 minutos, com cursor persistido, passando cada evento pelo mesmo caminho do webhook.
Dinheiro¶
- R21 MUST creditar depósito só em
paid, uma vez (idempotente porexternal_id), pelo valor que você registrou na criação. - R22 NEVER mover dinheiro em estado, tipo de evento,
method,kindou motivo desconhecido: trate como não-terminal, responda200, confira depois (Contrato §6.0). - R23 MUST reservar (debitar) o saldo do jogador antes de
POST /withdrawals, na mesma transação que grava o saque local. - R24 MUST devolver a reserva só em
rejected,failed,cancelled(lidos por fetch-back) ou em4xxconclusivo na criação. NEVER em timeout,5xxou429. - R25 NEVER debitar/recreditar automaticamente em
refunded,chargeback,returned,recalled,paid_partialou pagamento depois de expirar:needs_decision+ alerta, decisão humana. - R26 MUST representar dinheiro como string de inteiro na menor unidade (
"5000"= R$ 50,00). NEVER float.
Dados do jogador¶
- R27 MUST usar
player.external_refopaco e imutável. NEVER e-mail, telefone ou documento como id. - R28 MUST mandar
player.ip(IP do jogador) em todo saque; sem ele o saque nunca é automático. - R29 MUST ler as exigências por operação em
GET /currencies(method_details[].deposit_requirements) e mandar os campos exigidos (ex.:player.name+player.emailcom Mercado Pago;player.namecom MaximaxPay), vindos do cadastro — NEVER inventados. - R30 NEVER usar, fora de produção, e-mail ou documento de pessoa real (inclusive o seu). E-mail de teste só
em
.test,.invalidou.example; CPF de teste gerado com dígito válido. - R31 NEVER mostrar ao jogador o motivo de bloqueio ou de revisão (listas,
PAYER_MISMATCH…).
Segredos, rede e operação¶
- R32 MUST guardar segredo de chamada, segredo de webhook e chave privada mTLS em cofre/arquivo
0400do usuário do serviço. NEVER em repositório, imagem, log, ticket ou variável de front. - R33 MUST cadastrar todos os IPs de saída na allowlist antes de o tráfego mudar e usar mTLS em produção.
- R34 MUST registrar
Dracma-Trace-Idem todo log de chamada. NEVER logarDracma-Signatureinteiro, segredo, documento completo ou chave PIX em claro. - R35 MUST reconciliar diariamente (
GET /reconciliation, depois das 02:00 UTC), conferir digest e assinatura e tratar divergência como incidente — NEVER "consertar" o livro para bater. - R36 MUST apontar homologação para o sandbox (
key_test_) e produção para produção (key_live_).
2. Endpoints (código atual)¶
Base: https://<host>/v1 (hosts reais no onboarding). Todos exigem a assinatura §3; todo POST exige
Idempotency-Key.
| Método e path | Escopo | Sucesso | Erros típicos | Página |
|---|---|---|---|---|
GET /ping |
qualquer chave | 200 {ok, operation_id, environment, server_time} |
401, 403 | autenticacao.md |
GET /currencies |
read |
200 {data: [Moeda + method_details]} |
503 | depositos.md |
POST /deposits |
deposits:write |
201 (pending, ou failed) / 202 (created) |
400, 409, 422, 429, 503 | depositos.md |
GET /deposits |
read (listagem 5/s) |
200 {data, next_cursor} |
400 | depositos.md |
GET /deposits/{id} |
read |
200 Deposito |
404 | depositos.md |
POST /deposits/{id}/cancel |
deposits:write |
200 Deposito (cancelled) |
404, 409 | depositos.md |
POST /withdrawals |
withdrawals:write |
201 Saque (approved, under_review, requested) |
400, 409, 422, 503 | saques.md |
GET /withdrawals |
read (listagem 5/s) |
200 {data, next_cursor} |
400 | saques.md |
GET /withdrawals/{id} |
read |
200 Saque |
404 | saques.md |
POST /withdrawals/{id}/cancel |
withdrawals:write |
200 Saque (cancelled) |
404, 409 | saques.md |
GET /players/{external_ref}/limits?currency= |
read |
200 LimitesJogador |
400, 404, 503 | saques.md |
GET /balances |
read |
200 {data: [{available, pending_in, pending_out, as_of}]} |
503 | saques.md |
GET /events?after=&object_id=&limit= |
read (listagem 5/s) |
200 {data: [Evento], next_cursor} |
400 | webhooks.md |
GET /reconciliation?date=¤cy=&cursor=&limit= |
read |
200 (última página com totals, digest e header Dracma-Signature) |
400, 409 day_open, 503 |
reconciliacao.md |
GET /rules |
read |
200 conjunto vigente + ETag |
404, 503 | saques.md |
POST /rules/change-requests |
rules:propose |
202 {id, status: pending_approval} |
412 etag_mismatch |
autenticacao.md |
POST /fx/quotes |
deposits:write ou withdrawals:write |
— | 422 fx_not_enabled sempre |
erros-e-idempotencia.md |
GET /fx/quotes/{id} |
read |
— | 404 sempre (nenhuma cotação é emitida) |
referencia/openapi.md |
GET /receipts?status=&updated_since=&limit= |
read (listagem 5/s) |
200 {data: [], next_cursor: null} |
400 | referencia/openapi.md |
GET /receipts/{id} |
read |
— | 404 sempre (sem trilho bancário) |
referencia/openapi.md |
POST /withdrawals/{id}/recall |
withdrawals:write |
— | 400, 404, 409 invalid_state_transition (saque PIX não tem recall) |
saques.md |
POST /sandbox/deposits/{id}/pay |
deposits:write |
200 Deposito |
400, 404, 409, 503 | sandbox.md |
POST /sandbox/deposits/{id}/expire |
deposits:write |
200 Deposito (expired) |
404, 409 | sandbox.md |
POST /sandbox/withdrawals/{id}/{approve,reject,pay,fail,return} |
withdrawals:write |
200 Saque (estado atual) |
400, 404, 409, 503 | sandbox.md |
POST /sandbox/bank-transfers/incoming |
deposits:write |
— | 400 rail_not_enabled (corpo válido), 400 validation_failed |
referencia/openapi.md |
POST /sandbox/outage |
qualquer …:write |
200 {down} |
400 | sandbox.md |
GET /sandbox/webhooks |
read |
200 log de entregas |
503 | sandbox.md |
POST /sandbox/webhooks/redeliver |
qualquer …:write |
200 {event_id, scheduled} |
400, 404, 503 | sandbox.md |
As rotas wire/SWIFT/câmbio do contrato 1.1.0 existem no recorte PIX (linhas acima): nenhuma move dinheiro
nem cria objeto. Em produção, todo /sandbox/* responde 404.
3. Erros (code estável) e o que fazer¶
| HTTP | code |
Repetir? | Ação |
|---|---|---|---|
| 400 | validation_failed |
não | corrija o pedido (errors[].field/reason) |
| 400 | currency_not_enabled / method_not_enabled |
não | configuração da operação |
| 401 | invalid_signature / timestamp_out_of_window / replayed_nonce / unknown_key |
não | assinatura, relógio, nonce, chave/ambiente |
| 403 | ip_not_allowed / insufficient_scope / operation_suspended |
não | allowlist, escopo, suporte |
| 404 | not_found |
não | id ou operação errados (objeto alheio também é 404) |
| 409 | idempotency_key_reused |
não | bug: chave reusada com outro corpo |
| 409 | duplicate_external_id |
não | use o objeto em existing |
| 409 | invalid_state_transition |
não | consulte o estado atual |
| 409 | idempotency_in_progress |
sim | mesma chave depois do Retry-After |
| 409 | day_open |
sim, depois | agende para depois do Retry-After |
| 412 | etag_mismatch |
não | releia GET /rules |
| 413 | validation_failed (too_large) |
não | corpo ≤ 64 KiB |
| 422 | amount_below_minimum / amount_above_maximum / document_invalid / document_mismatch / player_blocked / destination_blocked / velocity_exceeded / insufficient_operation_balance / fx_not_enabled |
não | recusa de negócio; sem objeto; saque: devolva a reserva |
| 429 | rate_limited |
sim | mesma chave depois do Retry-After |
| 500 | internal_error |
sim | mesma chave; chamado com o trace_id se persistir |
| 503 | service_unavailable (e, no futuro, psp_unavailable / outside_operating_hours) |
sim | mesma chave depois do Retry-After |
Motivos de validação (errors[].reason) e o catálogo completo: erros-e-idempotencia.md.
4. Estados e efeito no livro do integrador¶
Depósito¶
| Estado | Terminal? | Efeito |
|---|---|---|
created |
não | nenhum |
pending |
não | nenhum (mostre o PIX) |
under_review |
não | nenhum |
paid |
para o crédito, sim | credita uma vez |
expired / cancelled / failed / returned |
sim | nenhum (mesmo com paid_amount preenchido) |
refunded / chargeback |
sim | débito por política: needs_decision |
pending_compliance / frozen |
não | nenhum |
paid_partial |
sim | nenhum automático: needs_decision |
| desconhecido | tratar como não | nenhum |
Saque (reserva feita antes do POST)¶
| Estado | Terminal? | Efeito na reserva |
|---|---|---|
requested / under_review / approved / processing |
não | mantém |
paid |
sim (pode ir a returned) |
finaliza |
rejected / failed / cancelled |
sim | devolve |
returned / recalled |
sim | nenhum automático: needs_decision |
pending_compliance / sent / in_transit / recall_requested |
não | mantém |
| desconhecido | tratar como não | mantém |
Eventos emitidos hoje¶
deposit.pending, deposit.under_review, deposit.paid, deposit.expired, deposit.cancelled,
deposit.failed, deposit.returned; withdrawal.under_review, withdrawal.approved, withdrawal.processing,
withdrawal.paid, withdrawal.rejected, withdrawal.failed, withdrawal.cancelled, withdrawal.returned.
Só no sandbox (ações refund/chargeback, D-D3): deposit.refunded, deposit.chargeback. Do contrato, ainda
sem gatilho: v1.1.0 (wire/SWIFT e screening):
deposit.pending_compliance, deposit.frozen, deposit.paid_partial, withdrawal.pending_compliance,
withdrawal.sent, withdrawal.in_transit, withdrawal.recall_requested, withdrawal.recall_rejected,
withdrawal.recalled, receipt.*.