Cobrança em cripto e links de pagamento¶
Para: Dev — Conferido em 2026-11-14.
Esta página descreve como cobrar em BRL e receber em cripto pela API (method: "crypto" em POST /v1/deposits), como mostrar ao jogador quanto ele paga antes de cobrar (GET /v1/fx/quote) e como criar um link de pagamento público (/v1/payment-links). Autenticação, Idempotency-Key, erros problem+json, paginação e webhooks são os mesmos do restante da API; aqui está só o que muda.
O que é¶
A cobrança continua em BRL; o jogador a quita no ativo escolhido (BTC.BTC, USDT.TRON e os demais de Cripto). A operação recebe o ativo, não BRL. O Dracma trava a cotação na criação, reserva um endereço único para a cobrança e devolve tudo o que o jogador precisa para pagar.
| Rota | Escopo | operationId |
Resposta |
|---|---|---|---|
GET /v1/fx/quote |
fx:read |
cotarCripto |
200 com a cotação indicativa (binding: false) |
POST /v1/deposits com method: "crypto" |
deposits:write |
criarDeposito |
201 com instructions.crypto, ou 202 em created sem instruções |
GET /v1/deposits/{id} |
read |
obterDeposito |
200 com o Deposito, incluindo crypto (progresso) |
POST /v1/payment-links |
payment_links:write |
criarLinkDePagamento |
201 com o LinkDePagamento |
GET /v1/payment-links |
payment_links:read |
listarLinksDePagamento |
200 com {data, next_cursor} |
GET /v1/payment-links/{id} |
payment_links:read |
obterLinkDePagamento |
200 com o LinkDePagamento |
POST /v1/payment-links/{id}/cancel |
payment_links:write |
cancelarLinkDePagamento |
200, idempotente |
Cripto vem desligada e é habilitada por operação. Com ela desligada, ou com o ativo fora da lista da operação, as rotas respondem 403 crypto_not_enabled. GET /v1/currencies mostra o que a sua operação aceita.
Saque em cripto
POST /v1/withdrawals não aceita cripto nesta versão. Esta página trata só de cobrança (depósito) e de links.
Quando usar¶
- Cobrança direta (
POST /v1/deposits): o seu produto já sabe o valor e o ativo e mostra o QR ao jogador, ou o leva aocheckout_url. - Cotação antes de cobrar (
GET /v1/fx/quote): para exibir "R$ 100,00 = 0,00021053 BTC" na tela de escolha. Não reserva nada. - Link de pagamento: você precisa de uma URL para mandar por mensagem ou e-mail e deixar o pagador escolher o método. O link não é uma cobrança: a cobrança nasce quando o pagador age na página pública.
Como funciona¶
Cotar: GET /v1/fx/quote¶
| Parâmetro | Obrigatório | Regra |
|---|---|---|
amount |
sim | BRL na menor unidade (centavos), inteiro em texto, por exemplo 10000 para R$ 100,00 |
currency |
não | só BRL (padrão) |
pay_currency |
sim | ativo, por exemplo BTC.BTC |
A resposta (CotacaoCripto) traz amount, pay_currency, pay_amount (unidade mínima do ativo), pay_amount_decimal, rate, mid_rate, spread_bps, quoted_at, expires_at e binding: false. Pontos que costumam enganar:
binding: falsequer dizer que nada foi travado. O valor travado só nasce noPOST /v1/deposits(ou na ação do pagador num link) e pode ser diferente do que a cotação mostrou.rateé BRL por 1 unidade maior do ativo (1 BTC), já com o spread. Não é a taxa inversa do câmbio de carteiras (POST /v1/fx/quotes, outra rota, outra semântica).spread_bpsé o da configuração da operação. Mandarspread_bpsna query é ignorado.- Dinheiro é sempre texto, nunca número JSON.
pay_amount_decimalé decimal exato, sem arredondamento de ponto flutuante. - A rota tem limite próprio de 60 por minuto por operação e responde
Cache-Control: no-store.
Criar a cobrança: POST /v1/deposits com method: "crypto"¶
O corpo é o mesmo de Depósitos, com estas regras a mais:
| Campo | Regra |
|---|---|
amount |
em BRL. Outra moeda responde 400 validation_failed com errors[0].reason = crypto_amount_must_be_brl |
method |
crypto |
method_options.crypto.pay_currency |
obrigatório: o ativo, dentre os habilitados da operação |
spread_bps, rate, pay_amount |
campos desconhecidos: 400. A cotação é do Dracma, nunca do cliente |
expires_in_seconds |
ignorado: a validade é a da cotação |
fx_quote_id |
422 fx_quote_not_applicable |
POST /v1/deposits HTTP/1.1
Host: api.example.test
Content-Type: application/json; charset=utf-8
Idempotency-Key: dep_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6b
Dracma-Key-Id: key_test_01J9EXEMPLO
Dracma-Timestamp: 1790000000
Dracma-Nonce: 0123456789abcdef0123456789abcdef
Dracma-Signature: v1=<assinatura>
{
"external_id": "dep_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6b",
"player": {
"external_ref": "0192a3b4-0000-7000-8000-000000000001",
"document": { "type": "CPF", "number": "12345678909", "country": "BR" }
},
"amount": { "amount": "10000", "currency": "BRL" },
"method": "crypto",
"method_options": { "crypto": { "pay_currency": "BTC.BTC" } }
}
O 201 traz status: "pending" e instructions.crypto (InstrucoesCripto):
{
"id": "dep_0192a3b40c6d7e8f9a0b1c2d3e4f5a6b",
"external_id": "dep_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6b",
"status": "pending",
"version": 2,
"amount": { "amount": "10000", "currency": "BRL" },
"method": "crypto",
"player_ref": "0192a3b4-0000-7000-8000-000000000001",
"provider": "chain",
"instructions": {
"crypto": {
"address": "tb1qexemplo0000000000000000000000000000",
"network": "bitcoin",
"pay_currency": "BTC.BTC",
"pay_amount": { "amount": "21053", "currency": "BTC.BTC" },
"pay_amount_decimal": "0.00021053",
"rate": "475000.00",
"mid_rate": "500000.00",
"spread_bps": 500,
"quoted_at": "2026-11-14T12:00:00Z",
"expires_at": "2026-11-14T12:15:00Z",
"payment_uri": "bitcoin:tb1qexemplo0000000000000000000000000000?amount=0.00021053",
"checkout_url": "https://pay.example.test/c/chk_exemploexemploexemploexemploexemploexe",
"confirmations_required": 3,
"testnet": true
}
},
"crypto": null,
"created_at": "2026-11-14T12:00:00Z",
"updated_at": "2026-11-14T12:00:01Z",
"expires_at": "2026-11-14T12:15:00Z"
}
Campo de instructions.crypto |
Uso |
|---|---|
address, network |
endereço único desta cobrança e a rede. Nunca reutilizado, sem memo nem tag |
pay_amount |
valor a enviar, em unidade mínima do ativo (satoshis, no exemplo), texto inteiro |
pay_amount_decimal |
o mesmo valor em decimal exato; é o que vai no QR |
rate, mid_rate, spread_bps |
cotação travada: com spread, sem spread e o spread aplicado |
quoted_at, expires_at |
validade da cotação; expires_at é também o expires_at do depósito |
payment_uri |
URI para o QR (BIP-21, EIP-681 ou URI TRON). Nem toda carteira lê a URI: mostre também endereço e valor em texto |
checkout_url |
página hospedada de pagamento. É uma URL-capacidade: quem a tem consegue vê-la. Trate como segredo curto |
confirmations_required |
confirmações da rede antes de paid |
testnet |
true fora de produção: o ativo é de rede de testes e não vale nada |
- Replay: a mesma
Idempotency-Keydevolve as mesmas instruções (mesmo endereço, mesma cotação, mesmoexpires_at), nunca uma cotação nova. 202: a cadeia ainda está reservando o endereço.statusvemcreatedeinstructionsvemnull. Esperedeposit.pendingou consulte o depósito; não crie outro.503 quote_unavailable: a cotação ou a rede está fora (Retry-After,reasonemsources_diverge,stale,insufficient_sources,upstream_down,invalid_rateoustore_unavailable). Nada foi criado. Repita com a mesmaIdempotency-Key.
Estados da cobrança em cripto¶
status |
Credita? | Significado |
|---|---|---|
pending |
não | instruções emitidas, aguardando o pagamento |
detected |
não | a rede viu uma transação para o endereço (0 confirmações) |
confirming |
não | já tem confirmação, mas menos que confirmations_required; crypto.confirmations mostra {current, required} |
paid |
sim | a soma dos recibos confirmados é igual a pay_amount e o primeiro foi visto até expires_at |
expired |
não | o prazo passou sem nenhum recibo visto |
under_review |
não | dinheiro em custódia à parte, esperando decisão manual (abaixo) |
Só paid credita. detected e confirming nunca movem dinheiro, nem no seu livro. Vale o instante em que o recibo foi visto, não o da confirmação: pagar dentro do prazo e confirmar depois do expires_at ainda resulta em paid.
under_review vem com crypto.review_reason e o equivalente em maiúsculas em review_reasons:
crypto.review_reason |
review_reasons |
Quando |
|---|---|---|
late_payment |
PAID_AFTER_EXPIRY |
recibo visto depois do expires_at |
amount_mismatch |
AMOUNT_MISMATCH |
soma confirmada diferente de pay_amount, para menos ou para mais. Não existe paid_partial em cripto |
wrong_asset |
WRONG_ASSET |
chegou ao endereço outro ativo suportado, por exemplo USDC.ETH numa cobrança ETH.ETH |
A revisão é resolvida no painel pelo Dracma. Não credite o jogador enquanto o estado for under_review.
Reorganização da rede. Antes de paid, a cobrança volta a pending sem crédito. Depois de paid, o Dracma emite deposit.chargeback: desfaça o crédito do seu lado quando o fetch-back mostrar chargeback.
O objeto crypto (progresso)¶
Preenchido só em method: "crypto", e null até haver recibo.
| Campo | Conteúdo |
|---|---|
txid |
identificador da transação, ou null |
confirmations |
{current, required} |
received |
soma on-chain recebida, em Money do ativo |
credited |
creditado à operação, no ativo. Só em paid |
spread_amount |
received menos credited. Só em paid |
detected_at, confirmed_at |
RFC 3339, ou null |
review_reason |
motivo de under_review, ou null |
O seu livro de jogador continua em BRL: ao ver paid no fetch-back, credite o jogador por amount (BRL), como em PIX. credited e spread_amount descrevem o que o Dracma credita à operação em ativo; não os use para creditar o jogador.
Webhooks¶
Assinados e entregues como os demais (veja Webhooks): at-least-once, com fetch-back obrigatório.
| Evento | Quando | data.object |
|---|---|---|
deposit.crypto.detected |
detected |
Deposito |
deposit.crypto.confirmations |
crypto.confirmations.current mudou (no máximo um por 30 s por cobrança; o último antes de paid sempre sai) |
Deposito |
deposit.paid, deposit.under_review, deposit.expired, deposit.cancelled, deposit.chargeback |
os existentes | Deposito |
payment_link.paid |
a cobrança do link ficou paid |
LinkDePagamento |
O receptor deve tratar tipo desconhecido com 200 e sem mover dinheiro. Para payment_link.paid, o last_charge_id aponta a cobrança: confira GET /v1/payment-links/{id} e GET /v1/deposits/{id} antes de creditar.
Links de pagamento¶
Um link é uma URL pública, https://pay.<domínio>/l/<slug>, que deixa o pagador escolher o método. O url devolvido pela API já vem completo. O slug (128 bits) é a capacidade pública e é diferente do id (plk_...); usar o slug no lugar do id nas rotas da API responde 404.
Corpo de POST /v1/payment-links (CriarLink; campo desconhecido é 400):
| Campo | Obrigatório | Regra |
|---|---|---|
external_id |
sim | seu id, até 64 caracteres, único por operação. Repetido com outra chave de idempotência: 409 duplicate_external_id |
player |
sim | o jogador, com documento, como em depósito. Fica fixado no link: todas as cobranças dele são atribuídas a esse jogador |
amount_type |
sim | fixed ou open |
amount |
se fixed |
Money em BRL. Proibido em open |
min_amount, max_amount |
se open |
Money em BRL, ambos obrigatórios, max_amount até o teto da operação. Proibidos em fixed |
allowed_methods |
sim | de 1 a 2 itens: {"method": "pix"} e/ou {"method": "crypto", "assets": ["BTC.BTC"]}. Em crypto, assets é obrigatório, sem repetição, e dentro dos ativos habilitados |
usage |
não | single (padrão: uma cobrança paga encerra o link) ou multi |
max_uses |
só multi |
de 2 a 100000 cobranças pagas que encerram o link. Ausente: ilimitado até expirar |
description |
não | até 140 caracteres, texto puro, sem HTML nem dado pessoal. É mostrada ao pagador |
expires_at |
não | padrão agora mais 7 dias; mínimo 5 minutos, máximo 90 dias |
metadata |
não | até 20 chaves, valores texto de até 256 caracteres |
{
"external_id": "link-2026-11-0001",
"player": {
"external_ref": "0192a3b4-0000-7000-8000-000000000001",
"document": { "type": "CPF", "number": "12345678909", "country": "BR" }
},
"amount_type": "fixed",
"amount": { "amount": "10000", "currency": "BRL" },
"allowed_methods": [
{ "method": "pix" },
{ "method": "crypto", "assets": ["BTC.BTC", "USDT.TRON"] }
],
"usage": "single",
"description": "Recarga de saldo",
"expires_at": "2026-11-21T12:00:00Z"
}
Resposta 201 (LinkDePagamento):
{
"id": "plk_01J9ZQ3EXEMPLO",
"external_id": "link-2026-11-0001",
"status": "active",
"version": 1,
"usage": "single",
"amount_type": "fixed",
"amount": { "amount": "10000", "currency": "BRL" },
"min_amount": null,
"max_amount": null,
"allowed_methods": [
{ "method": "pix" },
{ "method": "crypto", "assets": ["BTC.BTC", "USDT.TRON"] }
],
"player_ref": "0192a3b4-0000-7000-8000-000000000001",
"description": "Recarga de saldo",
"url": "https://pay.example.test/l/Abcdefghijklmnopqrstuv",
"max_uses": null,
"uses_paid": 0,
"last_charge_id": null,
"expires_at": "2026-11-21T12:00:00Z",
"paid_at": null,
"cancelled_at": null,
"metadata": {},
"created_at": "2026-11-14T12:00:00Z",
"updated_at": "2026-11-14T12:00:00Z"
}
status do link |
Significado |
|---|---|
active |
pode receber cobranças |
paid |
encerrado por pagamento (single, ou multi que atingiu max_uses) |
expired |
passou do expires_at |
cancelled |
cancelado por você |
- Em
singleexiste no máximo uma cobrança não terminal por vez. - Cancelar (
POST /v1/payment-links/{id}/cancel,Idempotency-Keyobrigatória):activepassa acancelled. Cobranças do link aindapending, sem pagamento detectado, são canceladas junto. Uma cobrança com pagamentodetectedouconfirmingsegue até o fim, porque o dinheiro já está a caminho; se ficarpaidnum linksingle, o link virapaid. Linkpaidouexpiredresponde409 invalid_state_transition. Repetir o cancelamento de um linkcancelleddevolve200com o mesmo corpo. - Listar (
GET /v1/payment-links): filtrosexternal_id,statuseplayer_ref, maiscursorelimit(1 a 200; fora disso400). Só aparecem links da sua operação.
O que o pagador vê na página pública¶
A página pay é só front: o servidor dela fala com a API por assinatura entre serviços, e nenhum segredo vai ao navegador. Ela mostra o valor em BRL, o ativo, a rede, o endereço, o valor exato a enviar, o QR, a cotação, a contagem regressiva e o estado, e se atualiza sozinha. Ela não mostra nem devolve: identificadores internos (dep_, plk_), o seu external_id, o jogador e o documento, metadata, o spread, a cotação sem spread, tarifas nem o nome do provedor.
- Um token ou
slugdesconhecido, malformado ou de outro ambiente responde404idêntico: não há como descobrir quais existem. - Link
paid,expiredoucancelledaberto na rota pública responde410 payment_link_unavailable. - O estado visível ao pagador segue os da tabela acima, em texto simples (por exemplo "Aguardando pagamento", "Confirmando", "Pagamento confirmado", "Pagamento em análise"). O pagamento só vale quando o seu fetch-back lê
paid. - Fora de produção o checkout mostra um aviso de rede de testes.
Erros¶
code |
HTTP | Repetir? | Quando |
|---|---|---|---|
crypto_not_enabled |
403 | não | cripto desligada na operação, ou ativo fora da lista habilitada |
asset_not_supported |
422 | não | ativo desconhecido ou fora das capacidades do provedor |
quote_unavailable |
503 | sim, com Retry-After |
fontes divergem, velhas ou insuficientes; cotação ou rede fora. O reason diz qual |
fx_quote_not_applicable |
422 | não | fx_quote_id num depósito crypto |
amount_out_of_range |
422 | não | valor fora da faixa mínima e máxima da operação (ou do link aberto) |
payment_link_unavailable |
410 | não | link pago, expirado ou cancelado, na rota pública |
payment_link_method_not_allowed |
422 | não | método ou ativo fora de allowed_methods |
invalid_state_transition |
409 | não | por exemplo cancelar um link já pago |
duplicate_external_id, idempotency_key_reused |
409 | não | os existentes |
Em POST /v1/deposits com cripto, a ordem de avaliação é: autenticação e escopo, validação do corpo, crypto_not_enabled, asset_not_supported, amount_out_of_range, regras, cotação (quote_unavailable) e endereço. O primeiro que falha decide a resposta. Os códigos estão no formato problem+json de Erros.
Erros comuns¶
| Sintoma | Causa provável | Correção |
|---|---|---|
403 crypto_not_enabled em operação nova |
cripto vem desligada | peça a habilitação ao time do Dracma e confira GET /v1/currencies |
400 ao mandar spread_bps ou rate |
a cotação é do Dracma | remova os campos |
Creditou o jogador em detected |
tratou "visto na rede" como pago | credite só com paid lido no fetch-back |
| Valor do QR diferente do que a cotação mostrou | GET /v1/fx/quote é indicativa |
mostre o valor de instructions.crypto, que é o travado |
404 ao consultar o link pelo url |
usou o slug no lugar do id |
consulte por plk_... |
| Pagou na rede errada | rede ou ativo diferente do indicado | o pagamento cai em wrong_asset ou pode não ser recuperável; avise o jogador na sua tela |
Veja também¶
- Depósitos, o fluxo comum a PIX e cripto.
- Cripto e Links de pagamento, a visão de quem opera e de quem paga.
- Catálogo de eventos e Idempotência.