Receptor passo a passo¶
Para: Dev — Conferido em 2026-10-18.
O receptor é o seu endpoint de webhook. Ele precisa verificar a assinatura, deduplicar, confirmar o estado na API e só então mover dinheiro, sempre de forma idempotente. Esta página traz a ordem exata dos 13 passos, o que cada resposta HTTP provoca no Dracma e o pseudocódigo de referência.
O que é¶
A ordem dos passos é a do operador de referência que a suíte de conformidade usa como oráculo (Suíte de conformidade). Seguir a ordem importa: a verificação barata e a de segurança vêm antes do trabalho caro (consulta à API e efeito no livro).
Quando usar¶
Use esta página como especificação ao escrever ou revisar o receptor, e como roteiro de teste: cada linha da tabela de passos é um caso a cobrir. Os vetores de assinatura cobrem os passos 1 a 5.
Como funciona¶
Os 13 passos, nesta ordem¶
| # | Passo | Se falhar |
|---|---|---|
| 1 | Leia o corpo cru, com um limite de tamanho folgado (o operador de referência lê até 64 KiB; sugestão: 256 KiB). | 400 |
| 2 | Confira se os headers Dracma-Timestamp e Dracma-Signature estão presentes. Interprete Dracma-Signature como pares chave=valor separados por vírgula: exatamente um t= e pelo menos um v1=. Ignore chaves desconhecidas, que podem ser versões futuras. |
401 |
| 3 | Janela: abs(agora − t) ≤ 300 s. O relógio do seu servidor precisa estar sincronizado por NTP. |
401 |
| 4 | O valor de Dracma-Timestamp tem de ser igual ao t=. |
401 |
| 5 | Assinatura: aceite se qualquer v1 bater com qualquer segredo de webhook ativo. Use comparação em tempo constante e percorra todos os pares, sem sair do laço no primeiro acerto. |
401 |
| 6 | Interprete o JSON. | 400 |
| 7 | Dracma-Event-Id tem de ser igual ao id do corpo. |
400 |
| 8 | Deduplicação: se o Dracma-Event-Id já foi processado com sucesso, responda 200 sem efeito. Guarde os ids por no mínimo 7 dias. |
200 |
| 9 | Fetch-back: para eventos de depósito ou saque, faça GET /v1/deposits/{object_id} ou GET /v1/withdrawals/{object_id} com a sua credencial. |
Erro de rede ou 5xx: responda 503 para o Dracma reentregar. 404: não mova dinheiro, marque o evento como visto e responda 200. |
| 10 | Coerência: confira que o external_id foi criado por você e que amount, currency e player_ref são idênticos aos que você registrou. |
Em divergência: não credite, marque o caso para revisão, dispare um alerta e responda 200. |
| 11 | Versão: se version for menor ou igual à última versão que você aplicou para esse objeto, não faça nada e responda 200. |
200 |
| 12 | Aplique o efeito do estado devolvido pela API (veja a tabela abaixo). O efeito precisa ser idempotente pelo external_id: no máximo um crédito por depósito. |
Erro interno: 5xx (reentrega) |
| 13 | Só depois do sucesso, marque o Dracma-Event-Id como visto e responda 200. |
A tabela de respostas na seção seguinte diz o que o Dracma faz com cada código.
O que cada resposta provoca no Dracma¶
| Você responde | O Dracma faz |
|---|---|
2xx (200 a 299), em até 10 s |
Dá a entrega como feita (delivered). Nada mais é enviado para esse evento. |
3xx |
Não segue o redirect. Conta como falha e repete. |
400, 401, 403, 404, 410, 413, 422 |
Conta como falha e repete no cronograma, por até 72 h. Nenhum 4xx para a retentativa. |
429 |
Falha, repete no cronograma normal. O Dracma não lê Retry-After. |
5xx (por exemplo 503) |
Falha, repete no cronograma. |
| Sem resposta em 10 s, conexão recusada, erro de TLS | Falha, repete. |
O cronograma, a janela de 72 h e a DLQ estão em Retentativas, skipped e DLQ. Pontos de atenção:
- Responda em até 10 s. Depois disso o Dracma trata a tentativa como falha, mesmo que o seu servidor termine o trabalho. Trabalho pesado vai para uma fila local; só responda 200 depois de gravar o evento de forma durável.
- O corpo da resposta é ignorado. Só o código de status importa; o Dracma descarta o corpo (lê no máximo 64 KiB).
- Erro "permanente" não existe para o Dracma. Responder
400ou401não faz o Dracma desistir: o evento volta até 15 ou 16 vezes e vai para a DLQ, com alerta. Para um evento que você não quer tratar, responda 200. - Nunca devolva a assinatura esperada nem o segredo na mensagem de erro.
Texto antigo da referência
A descrição do webhook na referência OpenAPI dizia que, em caso de 401, o Dracma "não insiste além da política normal e alerta". Isso induz a erro: o 401 é uma falha como qualquer outra, repetida até a DLQ. O alerta é o e-mail de DLQ ao fim do ciclo.
Fetch-back: por que é obrigatório¶
Suponha que o seu segredo de webhook vazou. Com ele alguém forja um deposit.paid com assinatura válida. Como você não credita pelo corpo e sim pelo GET feito com a sua credencial, o webhook forjado não passa de uma consulta inútil, e nenhum dinheiro é criado. A suíte de conformidade testa exatamente isto: um webhook forjado com assinatura válida não pode gerar crédito. O fetch-back também resolve a ordem: a consulta devolve sempre o estado mais recente, e o version do objeto ordena as aplicações.
Estado devolvido → ação¶
O que manda é sempre o estado devolvido pela consulta, não o type do evento. A tabela completa por evento está em Catálogo de eventos; o resumo por estado é:
| Estado do objeto | Ação |
|---|---|
Depósito pending |
Nenhum dinheiro se move. Mostre o QR Code. |
Depósito under_review |
Nenhum dinheiro se move. O desfecho vem depois. |
Depósito paid |
Credite o jogador pelo valor que você registrou, uma única vez. |
Depósito expired, cancelled, failed, returned |
Terminal, sem crédito. |
Depósito refunded, chargeback |
Débito de depósito creditado: política sua; recomendação de decisão manual com alerta. |
Saque under_review, approved, processing |
Nenhum dinheiro se move; a reserva continua. |
Saque paid |
A reserva vira saída definitiva; guarde psp_reference. |
Saque rejected, failed, cancelled |
Devolva a reserva ao jogador, uma única vez. |
Saque returned |
O banco devolveu depois do paid: política sua; recomendação de decisão manual com alerta. |
| Qualquer estado ou tipo desconhecido | Responda 200, grave o evento, não mova dinheiro e confirme depois. |
Idempotência entre "visto" e efeito¶
A gravação do "evento visto" e o efeito no seu livro precisam ser idempotentes entre si. Há duas formas de garanti-lo: gravar os dois na mesma transação, ou tornar o efeito idempotente pelo external_id (uma restrição de unicidade no lançamento). Assim, um crash entre um passo e outro não credita duas vezes. A segunda forma é a que protege também contra o mesmo evento chegando por webhook e por GET /events.
Tamanho do corpo¶
O Dracma não impõe um tamanho máximo ao corpo que envia. Na prática, o corpo é o envelope com um depósito ou um saque, alguns KiB, e o operador de referência da conformidade lê até 64 KiB. Configure o limite de leitura do receptor com folga (a sugestão do passo 1 é 256 KiB) e não responda com um erro de tamanho a um evento legítimo: como qualquer não-2xx, ele seria repetido até a DLQ.
Exemplos¶
Pseudocódigo¶
handler(req):
corpo = ler_cru(req, max=256KiB) ou 400
sig = parse(req.header["Dracma-Signature"]) ou 401 # t= único, >=1 v1
se |agora - sig.t| > 300: 401
se req.header["Dracma-Timestamp"] != sig.t: 401
ok = falso
para cada segredo em segredos_webhook_ativos: # rotação: 1 ou 2
esperado = HMAC_SHA256(segredo, sig.t + "." + corpo)
para cada v em sig.v1:
se tempo_constante_igual(hex_decode(v), esperado): ok = verdadeiro # sem break
se não ok: 401
ev = json(corpo) ou 400
se ev.id != req.header["Dracma-Event-Id"]: 400
se ja_processado(ev.id): 200 # dedup >= 7 dias
se ev.type começa com "deposit." ou "withdrawal.":
obj, status = GET /v1/{deposits|withdrawals}/{ev.object_id} # SUA credencial
se erro ou status >= 500: 503 # Dracma reentrega
se status == 404: marcar_visto(ev.id); 200
se não coerente(obj): revisar+alertar; marcar_visto(ev.id); 200
se obj.version <= versao_aplicada(obj.id): marcar_visto(ev.id); 200
aplicar_estado(obj) # idempotente por external_id, na mesma transação de:
marcar_visto(ev.id)
200
Há clientes executáveis com verificarWebhook em Go, Node, PHP e shell em Exemplos executáveis.
Tabelas mínimas¶
-- eventos já aplicados (retenção de pelo menos 7 dias)
CREATE TABLE dracma_eventos_vistos (
event_id text PRIMARY KEY,
visto_em timestamptz NOT NULL DEFAULT now()
);
-- o efeito financeiro nunca se repete para o mesmo depósito ou saque
CREATE UNIQUE INDEX lancamento_dracma_unico ON lancamentos (external_id, tipo);
Erros e casos de borda¶
- O receptor responde 401 a todos os eventos de repente. Quase sempre é rotação de segredo (o receptor só conhece um dos dois) ou relógio fora de sincronia. Veja Problemas comuns.
- Chegou um evento que você nunca criou. A coerência (passo 10) pega:
external_iddesconhecido. Não credite, alerte e responda 200. - O fetch-back devolve 404. O objeto não existe para a sua chave (pode ser de outra operação, ou um evento forjado). Não mova dinheiro; marque como visto e responda 200.
- O mesmo evento chega duas vezes ao mesmo tempo. Dois processos podem passar pelo passo 8 juntos. A restrição de unicidade no lançamento (passo 12) é a barreira final.
- A fila local enche. Responda 2xx só depois de gravar; se a fila não aceita mais, responda 503 e deixe o Dracma repetir.
- Fuso e relógio.
created_ateDracma-Timestampestão em UTC.
Veja também¶
- Requisição e assinatura: o cálculo da assinatura e os vetores.
- Envelope e Catálogo de eventos: o que cada campo e cada evento significa.
- Retentativas, skipped e DLQ: cronograma de reentrega.
- Recuperação com
GET /events: job que fecha lacunas. - Estados no seu produto: o que mostrar ao jogador em cada estado.