Receitas por caminho

Para: Dev — Conferido em 2026-10-18.

Doze receitas para provar, na operação Teste, cada caminho que o seu conector precisa tratar. Cada uma diz o que chamar, o que esperar e o que o seu livro deve fazer.

O que é

Uma receita é um roteiro curto: crie o objeto, dispare a ação de sandbox, espere o webhook, faça o fetch-back e confira o efeito no seu livro. Todas usam as rotas descritas em Sandbox.

Regras que valem para todas:

  • POST significa requisição assinada com Idempotency-Key. Em ações de sandbox use uma chave nova a cada ação.
  • Entre um passo e o seguinte, espere o webhook ou leia o objeto (fetch-back). Nunca assuma o estado pelo que você acabou de chamar.
  • Os corpos completos de criação estão em Depósitos e Saques.
  • Cada jogador pode pedir poucos saques por hora, também no sandbox (Limites e quotas). Para rodar várias receitas seguidas, use um external_ref de jogador diferente em cada uma.
  • Para sacar, a operação precisa de caixa, e o caixa só cresce com depósitos pagos. Antes de qualquer receita de saque, pague um depósito (receita 1). Sem caixa livre, a criação do saque responde 422 insufficient_operation_balance.

Quando usar

Rode todas antes do go-live e depois de cada mudança no seu receptor ou no seu livro. Em cada uma, confira três coisas: 1 crédito por depósito pago, 0 crédito quando o webhook é forjado ou reentregue, e reserva devolvida em rejected, failed e cancelled.

Como funciona

Ação, evento e versão

Cada transição de estado grava um evento e sobe object_version em 1. Use esta tabela para saber o que o seu receptor vai ver.

O que você faz Estado resultante Evento object_version
POST /v1/deposits pending (o objeto nasce created, que não gera evento) deposit.pending 2
.../pay exato, pagador igual ao jogador paid deposit.paid 3
.../pay com outro pagador ou outro valor under_review deposit.under_review 3
.../expire expired deposit.expired 3
POST /v1/deposits/{id}/cancel cancelled deposit.cancelled 3
.../refund (de paid) refunded deposit.refunded 4
.../chargeback (de paid) chargeback deposit.chargeback 4
POST /v1/withdrawals automático approved withdrawal.approved 1
POST /v1/withdrawals com revisão under_review withdrawal.under_review 1
.../approve approved withdrawal.approved +1
.../reject rejected withdrawal.rejected +1
Simulador envia o PIX (.../pay ou automático) processing withdrawal.processing +1
Simulador liquida paid withdrawal.paid +1
.../fail processing, depois failed withdrawal.processing, withdrawal.failed +1, +1
.../return (de paid) returned withdrawal.returned +1
  • No saque, a versão do primeiro evento é 1, exceto quando a chave PIX precisa de consulta ao DICT: o saque nasce requested (estado que não aparece em GET /v1/events nem em webhook), e a primeira versão visível é 2.
  • Compare sempre a versão do evento com a que você já aplicou, não com um número fixo. Eventos podem chegar fora de ordem: aplique só a versão maior.
  • O catálogo completo, com payloads, está em Catálogo de eventos.
stateDiagram-v2 direction LR [*] --> pending: POST /deposits pending --> paid: pay exato pending --> under_review: pay de terceiro ou valor diferente pending --> expired: expire pending --> cancelled: cancel paid --> refunded: refund paid --> chargeback: chargeback

Valor terminado em 77: saque que espera por você

Por padrão, o simulador paga o saque sozinho assim que ele é despachado: approved, processing e paid chegam em poucos segundos. Isso é útil para o caminho feliz, mas não deixa tempo para você agir.

Para controlar o desfecho, crie o saque com um valor cujos dois últimos dígitos na menor unidade são 77 (em BRL, centavos ,77; por exemplo "2077", R$ 20,77). O saque fica em processing e só muda quando você chamar pay, fail ou return. Sem esse gatilho, uma chamada a .../pay pode correr contra o pagamento automático e receber 409 invalid_state_transition.

Quero testar Crie o saque com Depois chame
Pagamento (receita 5) qualquer valor nada; o simulador paga
Pagamento explícito valor xx77 pay
Falha do PSP e devolução da reserva valor xx77 fail
Devolução depois de pago qualquer valor espere paid e chame return

O gatilho 77 só existe no simulador. Ele não afeta nenhuma operação Real.

Exemplos

Os exemplos de shell supõem dr como no Primeiro depósito e primeiro saque e as variáveis DEP_ID, DEP_EXT, WDR_ID.

Receita 1: depósito pago pelo próprio jogador, paid

  1. POST /v1/deposits com player.document igual ao CPF do jogador. A resposta é 201 com pending e instructions.pix, ou 202 com created, que passa a pending em seguida.
  2. POST /v1/sandbox/deposits/{id}/pay com corpo vazio. Resposta 200 com o objeto.
  3. Chega o webhook deposit.paid. Faça GET /v1/deposits/{id} e confira status: paid. Credite uma vez, por external_id.
  4. Confira que o evento aparece em GET /v1/events?object_id={id}.
dr POST "/sandbox/deposits/$DEP_ID/pay" "" "sbx_$(novo_id)"
dr GET "/deposits/$DEP_ID"
dr GET "/events?object_id=$DEP_ID"

Receita 2: pago por terceiro, under_review com PAYER_MISMATCH

  1. Crie o depósito (receita 1, passo 1).
  2. Chame pay com um payer_document diferente do documento do jogador, por exemplo outro CPF de teste.
  3. Resultado: under_review, com review_reasons contendo PAYER_MISMATCH. Nada é creditado e o webhook é deposit.under_review.
  4. A decisão (aprovar ou devolver) é de um revisor no painel (Revisão de depósitos e saques). Aprovado, o depósito vai a paid e aí você credita; devolvido, vai a returned e você não credita.
echo '{"payer_document":{"type":"CPF","number":"11144477735","country":"BR"}}' > pagador.json
dr POST "/sandbox/deposits/$DEP_ID/pay" pagador.json "sbx_$(novo_id)"

Receita 3: valor divergente, under_review com AMOUNT_MISMATCH

  1. Crie um depósito de 5000.
  2. Chame pay com {"paid_amount": {"amount": "4999", "currency": "BRL"}}. Moeda diferente da do depósito: 400 validation_failed com reason: currency_mismatch.
  3. Resultado: under_review com AMOUNT_MISMATCH. Nada é creditado e o webhook deposit.paid não é emitido para esse depósito.

Receita 4: expiração, expired

  1. Crie o depósito e espere pending.
  2. POST /v1/sandbox/deposits/{id}/expire: expired, com o webhook deposit.expired. expire só vale a partir de pending; fora disso, 409 invalid_state_transition.
  3. Chame pay depois: não leva a paid.
  4. POST /v1/deposits/{id}/cancel: 409 invalid_state_transition, porque expired é terminal.

Receita 5: saque automático, paid

  1. Tenha um jogador com um depósito paid (receita 1). A regra X× só conta o que o Dracma liquidou.
  2. Reserve o valor no seu livro e chame POST /v1/withdrawals com valor dentro do limite e review_requested: false. Mande player.ip: sem ele o saque nunca é automático (OWNERSHIP_UNVERIFIED).
  3. A resposta é 201 com approved e decision.mode: automatic. O simulador paga sozinho: chegam withdrawal.approved, withdrawal.processing e withdrawal.paid. Faça o fetch-back e finalize a reserva ao ler paid.

O primeiro saque pode ir para revisão

Dependendo das regras da operação (veja GET /v1/rules), o primeiro saque de um jogador vai para revisão com FIRST_WITHDRAWAL. Leia decision.reasons. Para chegar ao caminho automático, faça antes um primeiro saque e aprove-o (receita 8).

Receita 6: saque acima do limite, under_review com AUTO_MULTIPLIER_EXCEEDED

Peça um saque maior que o total depositado e liquidado vezes o multiplicador da operação. Resultado: under_review, decision.mode: manual e decision.reasons com AUTO_MULTIPLIER_EXCEEDED (ou NO_SETTLED_DEPOSIT, se o jogador nunca teve depósito liquidado). A reserva continua no seu livro.

Receita 7: review_requested: true nunca é automático

Repita a receita 5 com "review_requested": true. Resultado: under_review com OPERATOR_REVIEW_REQUESTED em decision.reasons, mesmo que as regras liberassem o automático. O operador só consegue tornar a decisão mais restritiva; nunca pular uma revisão.

Receita 8: decisões e resultado do PSP no saque

POST /v1/sandbox/withdrawals/{id}/{action}:

action De, para O que o seu livro faz
approve under_review para approved (o Dracma já despacha) Nada. A reserva continua.
reject requested ou under_review para rejected Devolve a reserva.
pay approved ou processing para paid (assíncrono) Finaliza a saída ao ler paid.
fail approved ou processing para failed (assíncrono) Devolve a reserva ao ler failed. O objeto traz failure_code.
return paid para returned (síncrono) A política é sua. Recomendação: nada automático; abra uma decisão manual e dispare um alerta.

Para pay e fail com tempo de agir, use o valor terminado em 77 (seção acima). approve só vale em under_review; ação fora da máquina de estados responde 409 invalid_state_transition. O cancelamento pelo operador é POST /v1/withdrawals/{id}/cancel: funciona em requested, under_review e em approved antes de o envio sair; depois disso, 409.

Chave PIX que não é CPF ou CNPJ

No simulador, o titular de uma chave email, phone ou random é um CPF sintético estável, derivado da chave, e não o do jogador. Com uma chave dessas, o saque tende a voltar 422 document_mismatch ou rejected. Para o caminho feliz use key_type: cpf com o CPF do próprio jogador. Para testar a recusa por titularidade, use uma chave cpf de outra pessoa.

Receita 9: queda e retomada sem duplicar

  1. POST /v1/sandbox/outage com {"down": true}. Daí em diante, toda rota da operação fora de /sandbox/* responde 503 (service_unavailable, retryable: true, Retry-After: 5). GET /v1/ping continua respondendo.
  2. POST /v1/deposits com Idempotency-Key: K: 503.
  3. POST /v1/sandbox/outage com {"down": false} e uma chave de controle nova.
  4. Repita o passo 2 com a mesma K e o mesmo corpo: 201 ou 202. Repita mais uma vez: a mesma resposta, agora com Idempotent-Replayed: true.
  5. GET /v1/deposits?external_id=...: exatamente 1 depósito.

É esse o comportamento que o seu cliente precisa ter em produção: diante de 5xx, 429 ou erro de rede, repita com a mesma chave. Há um helper pronto em Exemplos executáveis.

Receita 10: reentrega de webhook, teste da sua deduplicação

  1. Rode uma receita que gere webhook (a 1) e anote o Dracma-Event-Id.
  2. POST /v1/sandbox/webhooks/redeliver com {"event_id": "evt_..."}: 200 com scheduled.
  3. O seu receptor recebe o mesmo Dracma-Event-Id e responde 200 sem efeito. O crédito continua sendo 1.
  4. GET /v1/sandbox/webhooks mostra a entrega e o status HTTP que você devolveu.

Receita 11: webhook forjado não cria dinheiro

Assine você mesmo um deposit.paid com o seu segredo de webhook, apontando para um depósito que não está pago, e envie ao seu receptor. A assinatura é válida, mas o fetch-back devolve pending. Resultado esperado: 200 e nenhum crédito. Veja Receptor passo a passo.

Receita 12: estorno e chargeback depois de paid

  1. Tenha um depósito paid já creditado no seu livro (receita 1).
  2. POST /v1/sandbox/deposits/{id}/refund ou .../chargeback, com corpo vazio ou {}. A resposta é 200 com o objeto em refunded ou chargeback e version maior. Fora de paid: 409 invalid_state_transition. Campo no corpo: 400.
  3. Chega deposit.refunded ou deposit.chargeback. Faça o fetch-back e aplique a sua política de débito. Recomendação: abra uma decisão manual e dispare um alerta; nunca debite sozinho (Política de exceções).

Só existe no sandbox

Estorno e chargeback são gatilhos de simulação. Em produção não há um gatilho equivalente hoje: o seu conector precisa tratar os dois estados, mas não os verá acontecer. Veja Devoluções, estornos e disputas.

Erros e casos de borda

Sintoma Causa provável O que fazer
pay em saque devolve 409 invalid_state_transition O simulador já pagou sozinho Crie o saque com valor xx77
422 insufficient_operation_balance ao criar saque Caixa da operação sem saldo livre Pague antes um depósito (receita 1)
429 rate_limited ao criar saque Muitos saques do mesmo jogador na hora Use outro external_ref ou espere o Retry-After
Saque sempre under_review Falta player.ip, primeiro saque ou chave nova Leia decision.reasons
422 document_mismatch no saque Chave PIX de outro titular, ou chave não-CPF no simulador Use chave cpf do próprio jogador
Depósito created por muito tempo A cobrança ainda está sendo criada Espere o webhook deposit.pending ou leia o objeto

Veja também