Saques¶
Para: Dev — Conferido em 2026-10-18.
Um saque é um pedido de pagamento PIX ao jogador. O motor de regras do Dracma decide na hora se ele sai no
automático ou vai para revisão humana, e o resultado final chega por webhook. Esta página descreve as quatro rotas
(criar, consultar, listar e cancelar), o destino PIX, as recusas na ordem em que a API as confere e os campos do objeto
Saque, inclusive o objeto return de um saque devolvido pelo banco.
O que é¶
| Rota | Escopo | operationId |
Resposta de sucesso |
|---|---|---|---|
POST /v1/withdrawals |
withdrawals:write |
criarSaque |
sempre 201 com o Saque e a decisão |
GET /v1/withdrawals |
read |
listarSaques |
200 com {data, next_cursor} |
GET /v1/withdrawals/{id} |
read |
obterSaque |
200 com o Saque |
POST /v1/withdrawals/{id}/cancel |
withdrawals:write |
cancelarSaque |
200 com o Saque em cancelled |
O quinto POST, POST /v1/withdrawals/{id}/recall, é uma rota reservada e está em
Rotas e campos reservados.
Hoje só existe PIX em BRL.
method |
Situação |
|---|---|
pix |
disponível |
card, crypto, boleto, bank_transfer, wire, swift |
não disponível: 400 method_not_enabled |
| qualquer outro valor | 400 validation_failed com field: method e reason: enum |
Não disponível
O Dracma não paga saque por cartão, cripto, boleto, transferência bancária, wire nem SWIFT. A tentativa responde
400 method_not_enabled e não cria nada. O único destino aceito é uma chave PIX.
O PSP que paga o saque é escolhido pelo Dracma para a sua operação. Isso não muda nada na API. Veja PSPs.
Quando usar¶
Regra nº 1: reserve antes de pedir. Debite (reserve) o saldo do jogador antes de chamar POST /v1/withdrawals, na mesma
transação em que você grava o saque no seu lado (external_id, valor e destino). Assim:
- o jogador não consegue apostar nem sacar de novo o mesmo dinheiro enquanto o saque está em voo;
- se a chamada falhar por rede ou
5xx, o dinheiro continua reservado e você repete com a mesmaIdempotency-Key, sem risco de pagar duas vezes; - a reserva só volta ao jogador quando o Dracma disser, de forma conclusiva, que o saque não sai: um
4xxsíncrono, ou o estadorejected,failedoucancelledlido pela API.
Nunca devolva a reserva sem resposta conclusiva. Timeout, erro de rede, 5xx e 429 não são conclusivos: repita com a mesma chave
ou consulte GET /v1/withdrawals?external_id=<seu id>.
- Transação local: grave o saque e reserve o saldo.
POST /v1/withdrawalscomIdempotency-Keyigual aoexternal_id,player.ipdo jogador e timeout de 10 s.201: guardeid,statusedecision.4xx: devolva a reserva. Rede,5xxou429: repita com a mesma chave.- Webhook
withdrawal.*: verifique, deduplique, faça o fetch-back emGET /v1/withdrawals/{id}e aplique a tabela de estados. - Um job com
GET /v1/events?after=a cada 5 minutos e a reconciliação diária fecham as lacunas.
A tabela de efeito de cada estado na reserva está em Ciclo do saque.
Como funciona¶
Criar: POST /v1/withdrawals¶
O corpo é um objeto JSON. A validação é estrita (a mesma do depósito): campo desconhecido, chave repetida, UTF-8 inválido, tipo errado
e conteúdo depois do objeto voltam 400 validation_failed com errors[].reason. Veja Depósitos para a lista.
| Campo | Obrigatório | Regra |
|---|---|---|
external_id |
sim | único por operação, ^[A-Za-z0-9_.:-]{1,64}$ |
player |
sim | mesmo formato do depósito. O documento fica fixado no primeiro uso. name até 120 caracteres, device_id até 128 |
player.ip |
na prática, sim | IP do jogador, não do seu servidor. Sem ele o saque nunca é automático: vai para under_review com OWNERSHIP_UNVERIFIED. IP inválido: 400 com reason: format |
amount |
sim | Money com amount maior que zero |
method |
sim | pix |
destination |
sim | exatamente um destino, com a mesma chave de method: {"pix": {"key_type", "key"}} |
review_requested |
não | true obriga revisão manual (OPERATOR_REVIEW_REQUESTED). Só torna a decisão mais restritiva |
review_reasons |
não | seus motivos; aparecem no painel com o prefixo OP: |
player_context |
não | total_deposited, total_withdrawn, account_created_at, net_gaming_result. Informativo: nunca libera o automático |
metadata |
não | até 20 chaves, valores texto |
player.email, player.birth_date e player.kyc_level são aceitos pela forma do Jogador, mas o saque não os usa.
purpose, fx_quote_id e payout_currency também são aceitos pela forma e ignorados: o saque PIX não tem câmbio
(veja Divergências conhecidas).
Destino PIX¶
destination.pix tem key_type e key. O key_type é um dos cinco abaixo; o Dracma normaliza a chave antes de comparar e de guardar.
key_type |
Chave | Normalização feita pelo Dracma |
|---|---|---|
cpf |
CPF do titular | só dígitos |
cnpj |
CNPJ | só dígitos |
email |
minúsculas | |
phone |
telefone | + seguido dos dígitos |
random |
chave aleatória (EVP) | minúsculas, sem espaços |
keytem de 1 a 140 caracteres. Fora disso:400comreason: format.key_typefora da tabela:400comreason: enum.- Se você tem um cadastro com nomes antigos de tipo (
evp,aleatoria,telefone), converta pararandomephoneantes de enviar. destinationcom mais de uma chave:reason: one_of. Chave diferente demethod:reason: method_mismatch.- A titularidade é conferida na decisão: chave
cpfprecisa ser o CPF do jogador;cnpjpara pessoa física é bloqueio;email,phoneerandomsão conferidas na consulta ao diretório do PIX. Com a consulta indisponível, a resposta é201comstatus: "requested"; o Dracma reconsulta por alguns minutos e depois decide (approved,rejectedse o titular diverge, ouunder_review).
Resposta 201 e a decisão¶
O código responde sempre 201 na criação, nunca 202: a criação não espera o PSP. O status da resposta é um destes:
status |
Quando | decision.mode |
decision.decided_at |
|---|---|---|---|
approved |
o motor decidiu o automático | automatic |
preenchido |
under_review |
decisão manual (regra, review_requested, sem player.ip, motor fora ou resposta incoerente) |
manual |
null |
requested |
consulta ao diretório do PIX pendente | manual, com reasons: [] |
null |
- O saque nasce com
version1já no estado decidido. Não há evento públicowithdrawal.requested. approvedfora do horário do automático espera a abertura da janela. Antes do envio o Dracma reconfere listas e caixa: se uma lista pegar, o saque vai acancelledcomBLOCKLIST_HIT_AT_DISPATCH; se faltar caixa, fica emapprovede o Dracma alerta.- O resultado final (
paid,failed,rejected...) chega por webhookwithdrawal.*. Aja só depois do fetch-back. - Motor de regras fora na criação não é
503: o saque é criado emunder_reviewcomRULES_UNAVAILABLE(na dúvida, revisão humana). Resposta incoerente do motor resulta emRULES_INVALID_RESPONSE. - O significado de cada código de
decision.reasonsestá em Decisão, limites e regras. Os limiares são da regra da operação: leia com Regras (API).
Recusas síncronas 422 (sem objeto)¶
Bloqueio duro responde 422 sem criar o saque. É uma decisão: devolva a reserva e não repita. A mesma Idempotency-Key repete o mesmo 422;
uma nova tentativa exige external_id novo (e nova reserva). Bloqueio com código fora desta lista vira revisão, não 422.
code |
Quando |
|---|---|
amount_below_minimum, amount_above_maximum |
fora do mínimo ou máximo por transação |
document_invalid |
CPF ou CNPJ com dígito verificador errado |
document_mismatch |
documento diferente do fixado, ou chave PIX de outro titular |
player_blocked |
jogador em lista de bloqueio |
destination_blocked |
chave PIX em lista de bloqueio |
velocity_exceeded |
limite duro de janela ou velocidade |
insufficient_operation_balance |
o caixa da operação no Dracma não cobre o saque. Pode repetir mais tarde, com Idempotency-Key e external_id novos |
Consultar: GET /v1/withdrawals/{id}¶
Escopo read. É o estado autoritativo do fetch-back. 200 devolve o Saque; 404 not_found quando o id não existe nesta operação (inclusive
objeto de outra operação) ou está fora do formato wdr_ seguido de letras e números.
Listar: GET /v1/withdrawals¶
Escopo read, com o balde de listagem do rate limit (veja Limites e quotas).
| Parâmetro | Uso |
|---|---|
external_id |
acha o saque pelo seu id. É como você descobre o destino de um POST cuja resposta se perdeu |
player_ref |
saques de um jogador |
status |
enum StatusSaque. Valor fora do enum: 400 com reason: enum |
updated_since |
RFC 3339 (aceita fração de segundo). Devolve updated_at maior ou igual ao instante. Formato inválido: reason: format |
cursor |
o next_cursor da página anterior. Inválido: reason: format |
limit |
de 1 a 200. Padrão 50. Fora da faixa: reason: range |
Resposta: {"data": [Saque, ...], "next_cursor": "..." | null}. A ordem é por created_at e depois id, crescente. O next_cursor é opaco
(hoje um texto em base64url): não monte nem interprete. Veja Paginação e filtros.
Cancelar: POST /v1/withdrawals/{id}/cancel¶
Escopo withdrawals:write. Corpo vazio. Idempotency-Key obrigatória.
- Permitido em
requested,under_reviewe emapprovedenquanto o comando de envio ao PSP ainda não saiu. A resposta é200com o objeto emcancelled, ou o mesmo200se ele já estavacancelled. Devolva a reserva quando lercancelled. approvedcom o envio já publicado,processingou qualquer estado depois:409 invalid_state_transition. O dinheiro pode já estar saindo; espere o desfecho (paidoufailed). Isso é mais restritivo que o contrato (D-W2 em Divergências conhecidas).idinexistente nesta operação:404 not_found.
Objeto Saque¶
O Saque é o mesmo no 201, no GET e no data.object do webhook. Os campos da v1.1.0 vêm sempre null ou vazios em PIX, com uma exceção: return.
| Campo | Tipo | Conteúdo |
|---|---|---|
id |
texto | wdr_ seguido de 32 caracteres hexadecimais. Trate como opaco |
external_id |
texto | o seu id |
status |
texto | estado atual |
version |
inteiro | começa em 1 e sobe a cada transição. Ignore estado com version menor ou igual à já aplicada |
amount |
Money |
valor pedido |
fee |
null |
sempre null: o Dracma não desconta tarifa do saque |
method |
texto | pix |
player_ref |
texto | o seu player.external_ref |
decision |
objeto | mode (automatic ou manual), reasons, ruleset_version, decided_by (ids opacos dos revisores) e decided_at |
psp_reference |
texto ou null |
identificador do pagamento PIX (E2E), preenchido quando o saque é pago |
failure_code |
texto ou null |
motivo técnico em failed. Lista aberta |
created_at, updated_at, paid_at |
texto | RFC 3339 em UTC. Podem trazer fração de segundo (diferente do depósito) |
return |
objeto ou null |
preenchido só em returned. Veja abaixo |
rail, fees, charge_bearer, fx, purpose, compliance, expected_settlement_date, sent_at, tracking, recall |
null, [] |
reservados da v1.1.0 |
Faça o parse de datas aceitando ou não fração de segundo (leitor tolerante).
Reservado no contrato
rail, fees, charge_bearer, fx, purpose, compliance, expected_settlement_date, sent_at, tracking e recall estão no schema
Saque, mas a API não os preenche em saque PIX. fees vem [] e os demais null. fee vem sempre null: as tarifas estão no contrato com o Dracma
(veja Tarifas).
status e transições¶
status |
Significado | Efeito na sua reserva |
|---|---|---|
requested |
decisão em curso (consulta ao diretório do PIX) | mantém |
under_review |
revisão humana; motivos em decision.reasons |
mantém |
approved |
aprovado; aguarda envio (horário do automático, caixa) | mantém |
processing |
enviado ao PSP | mantém |
paid |
pago | finaliza: a reserva vira saída definitiva |
rejected, failed, cancelled |
o dinheiro não saiu | devolve a reserva |
returned |
o banco devolveu depois de paid |
decisão sua. Veja Ciclo do saque |
pending_compliance, sent, in_transit, recall_requested, recalled |
acréscimos da v1.1.0 | não alcançáveis em PIX; só existem como filtro de status |
| desconhecido | — | mantém a reserva e confira de novo (leitor tolerante) |
Devolver e finalizar são idempotentes pelo external_id: dois eventos do mesmo desfecho movem uma vez.
O objeto return¶
Quando o banco devolve um saque já pago, o saque vai de paid para returned e o campo return passa a vir preenchido:
| Campo | Tipo | Conteúdo |
|---|---|---|
reason_code |
texto | código curto do motivo informado pelo PSP (letras minúsculas, números e _, até 64 caracteres). unspecified quando o PSP não informou |
returned_amount |
Money |
o valor devolvido. Pode ser menor que amount (devolução parcial) e nunca é maior |
returned_at |
texto | instante da devolução, RFC 3339 em UTC |
- Devolução parcial continua
returned: não existe um estado "parcialmente devolvido". Confirareturned_amountcontraamount. - O Dracma credita de volta ao caixa da operação só o valor devolvido e registra um item
returnna reconciliação (veja Reconciliação). reasonereturn_feedo schemaDevolucaoBancarianão são devolvidos em saque PIX.- Fora de
returned,returnénull.
O que fazer com o jogador quando vem returned é política sua. A recomendação é não mover dinheiro automaticamente: marque como exceção, alerte e decida com um humano
(veja Política de exceções de dinheiro).
Exemplos¶
Pedido de criação (os valores de assinatura são ilustrativos):
POST /v1/withdrawals HTTP/1.1
Host: api.example.test
Content-Type: application/json; charset=utf-8
Idempotency-Key: wdr_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6c
Dracma-Key-Id: key_test_01J9EXEMPLO
Dracma-Timestamp: 1790000000
Dracma-Nonce: 0123456789abcdef0123456789abcdef
Dracma-Signature: v1=<assinatura>
{
"external_id": "wdr_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6c",
"player": {
"external_ref": "0192a3b4-0000-7000-8000-000000000001",
"document": { "type": "CPF", "number": "12345678909", "country": "BR" },
"name": "Jogador Teste",
"ip": "203.0.113.10",
"device_id": "dev_7f3a"
},
"amount": { "amount": "5000", "currency": "BRL" },
"method": "pix",
"destination": {
"pix": { "key_type": "cpf", "key": "12345678909" }
},
"review_requested": false
}
Resposta 201, decisão automática:
{
"id": "wdr_0192a3b40c6d7e8f9a0b1c2d3e4f5a6c",
"external_id": "wdr_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6c",
"status": "approved",
"version": 1,
"amount": { "amount": "5000", "currency": "BRL" },
"fee": null,
"method": "pix",
"player_ref": "0192a3b4-0000-7000-8000-000000000001",
"decision": {
"mode": "automatic",
"reasons": [],
"ruleset_version": 7,
"decided_by": [],
"decided_at": "2026-10-18T15:00:01.250Z"
},
"psp_reference": null,
"failure_code": null,
"created_at": "2026-10-18T15:00:00.812Z",
"updated_at": "2026-10-18T15:00:01.250Z",
"paid_at": null,
"rail": null,
"fees": [],
"charge_bearer": null,
"fx": null,
"purpose": null,
"compliance": null,
"expected_settlement_date": null,
"sent_at": null,
"tracking": null,
"return": null,
"recall": null
}
Resposta 201, revisão manual (primeiro saque e chave nova):
{
"id": "wdr_0192a3b40c6d7e8f9a0b1c2d3e4f5a6d",
"external_id": "wdr_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6d",
"status": "under_review",
"version": 1,
"amount": { "amount": "5000", "currency": "BRL" },
"fee": null,
"method": "pix",
"player_ref": "0192a3b4-0000-7000-8000-000000000002",
"decision": {
"mode": "manual",
"reasons": ["FIRST_WITHDRAWAL", "PIX_KEY_CHANGED"],
"ruleset_version": 7,
"decided_by": [],
"decided_at": null
},
"psp_reference": null,
"failure_code": null,
"created_at": "2026-10-18T15:02:00.100Z",
"updated_at": "2026-10-18T15:02:00.100Z",
"paid_at": null,
"rail": null,
"fees": [],
"charge_bearer": null,
"fx": null,
"purpose": null,
"compliance": null,
"expected_settlement_date": null,
"sent_at": null,
"tracking": null,
"return": null,
"recall": null
}
Um saque devolvido pelo banco, com devolução parcial (R$ 30,00 de R$ 50,00):
{
"id": "wdr_0192a3b40c6d7e8f9a0b1c2d3e4f5a6c",
"external_id": "wdr_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6c",
"status": "returned",
"version": 5,
"amount": { "amount": "5000", "currency": "BRL" },
"fee": null,
"method": "pix",
"player_ref": "0192a3b4-0000-7000-8000-000000000001",
"decision": {
"mode": "automatic",
"reasons": [],
"ruleset_version": 7,
"decided_by": [],
"decided_at": "2026-10-18T15:00:01.250Z"
},
"psp_reference": "E00000000202610181500abcdef0123456",
"failure_code": null,
"created_at": "2026-10-18T15:00:00.812Z",
"updated_at": "2026-10-19T09:30:12.004Z",
"paid_at": "2026-10-18T15:00:09.500Z",
"rail": null,
"fees": [],
"charge_bearer": null,
"fx": null,
"purpose": null,
"compliance": null,
"expected_settlement_date": null,
"sent_at": null,
"tracking": null,
"return": {
"reason_code": "unspecified",
"returned_amount": { "amount": "3000", "currency": "BRL" },
"returned_at": "2026-10-19T09:30:11Z"
},
"recall": null
}
Recusa 422, caixa insuficiente (nenhum saque foi criado):
{
"type": "https://docs.dracma.test/errors/insufficient_operation_balance",
"title": "insufficient_operation_balance",
"status": 422,
"code": "insufficient_operation_balance",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"retryable": false
}
Para descobrir o destino de um POST cuja resposta se perdeu:
GET /v1/withdrawals?external_id=wdr_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6c HTTP/1.1
Dinheiro real
Em uma operação Real, POST /v1/withdrawals paga PIX de verdade. Faça os testes de aprovação, recusa, pagamento, falha e devolução numa
operação Teste, com as ações de Sandbox.
Erros e casos de borda¶
Erros da criação, na ordem em que a API os confere¶
| # | HTTP | code |
Quando | O que fazer |
|---|---|---|---|---|
| 1 | 400 | validation_failed |
corpo inválido: forma, method ausente ou fora do enum (enum), destino (one_of, method_mismatch, enum, format), player.ip inválido (format) |
corrija; devolva a reserva |
| 2 | 429 | rate_limited |
mais de 3 saques pedidos por jogador por hora | mantenha a reserva e repita depois do Retry-After, com a mesma chave |
| 3 | 400 | currency_not_enabled |
moeda não habilitada na operação | configuração do onboarding; devolva a reserva |
| 4 | 400 | method_not_enabled |
method diferente de pix, ou método não habilitado |
devolva a reserva |
| 5 | 409 | duplicate_external_id |
o external_id já existe; o objeto vem em existing |
siga pelo estado do existing; não crie outro |
| 6 | 422 | os da tabela de recusas | bloqueio duro da decisão | devolva a reserva; não repita igual |
| 7 | 503 | service_unavailable |
cofre de dados ou banco fora | repita com a mesma chave; mantenha a reserva |
- Rede, timeout,
5xxe429: repita com a mesmaIdempotency-Keyaté uma resposta conclusiva e não devolva a reserva. Na dúvida, consulteGET /v1/withdrawals?external_id=. 409 idempotency_key_reused: mesma chave com corpo diferente. É um bug do seu lado: mantenha a reserva e investigue.- Regras de repetição e retenção da chave: Idempotência e repetição.
Outras rotas¶
| Rota | Situação | Resposta |
|---|---|---|
GET /v1/withdrawals/{id} |
não existe nesta operação, ou id fora do formato |
404 not_found |
GET /v1/withdrawals |
status, updated_since, limit ou cursor inválidos |
400 validation_failed |
POST /v1/withdrawals/{id}/cancel |
envio já publicado, processing ou depois |
409 invalid_state_transition |
| qualquer | sem o escopo exigido | 403 insufficient_scope |
Casos de borda¶
approvedpode durar. Se o envio é recusado antes de sair (caixa insuficiente ou guarda de ambiente), o saque fica emapprovede o Dracma alerta. Mantenha a reserva.requestednão é revisão. Espere: o Dracma decide sozinho depois de alguns minutos.- Sem
player.ipo saque vai sempre para revisão. Mande o IP do jogador em todo pedido. - Estado, motivo ou campo desconhecido nunca devem derrubar o seu parser: trate como não-terminal, mantenha a reserva e confira de novo pelo fetch-back.
- Datas de saque podem ter fração de segundo; as de depósito não.
Veja também¶
- Ciclo do saque: reserva,
processing,approvedparado ereturned. - Decisão, limites e regras:
decision.reasons. - Limites do jogador e saldos: quanto cabe no automático e o caixa da operação.
- Rotas e campos reservados: recall e campos da v1.1.0.
- Erros, Idempotência e repetição e Webhooks
- Sandbox