Autenticação HMAC

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

Toda chamada à API /v1 leva uma assinatura HMAC-SHA256 calculada sobre o método, o caminho, o corpo, um carimbo de tempo e um nonce. Esta página mostra os cabeçalhos, o pedido canônico, o exemplo trabalhado (com os vetores públicos), a ordem em que o servidor confere e o que cada 401 e 403 significa.

O que é

A chave de API tem duas partes: o key_id (key_test_… ou key_live_…, público, vai no cabeçalho) e o segredo (sk_test_… ou sk_live_…, que nunca sai do seu servidor). O segredo assina; o key_id diz ao Dracma com qual segredo conferir. De posse de uma assinatura válida, o Dracma sabe qual operação está chamando: tenant e operação vêm só da chave, nunca do corpo.

A assinatura é a camada de autenticação que vale em toda chamada. Camadas de rede opcionais (mTLS e lista de IPs) estão descritas em Rede, lista de IPs e mTLS.

Não disponível

O Dracma não aplica a lista de IPs às chamadas hoje. A tela de IPs do painel registra a lista, mas as chamadas não são recusadas por IP. Não construa o seu controle de acesso em cima de 403 ip_not_allowed: ele existe no catálogo de erros, mas você não deve vê-lo. Veja IPs do conector.

Cada chave também tem escopos: uma chave só de leitura não cria depósito (seção Escopos, abaixo).

Quando usar

Em toda chamada a /v1/*, inclusive GET /v1/ping e as rotas /v1/sandbox/*: nenhuma rota de /v1 dispensa assinatura. O documento OpenAPI não passa pela API; baixe-o em OpenAPI.

Como funciona

Os quatro cabeçalhos

Dracma-Key-Id:    key_test_01J9ABCDEF         (key_live_… em operação Real)
Dracma-Timestamp: 1790000000                  (segundos Unix, UTC)
Dracma-Nonce:     0123456789abcdef0123456789abcdef
Dracma-Signature: v1=a58fa1956941157aad72536512b2b09f234de3670050c865071ddc56df3b4964
Cabeçalho Formato exato aceito
Dracma-Key-Id key_test_ ou key_live_ seguido de 1 a 64 caracteres [A-Za-z0-9]
Dracma-Timestamp só dígitos ASCII, sem sinal, sem espaço e sem zero à esquerda; em segundos (não milissegundos)
Dracma-Nonce exatamente 32 caracteres hexadecimais minúsculos (^[0-9a-f]{32}$), isto é, 16 bytes de gerador criptográfico
Dracma-Signature v1= seguido de 64 hexadecimais minúsculos

Todo POST leva também Idempotency-Key (Idempotência e repetição).

O prefixo do key_id diz em que tipo de operação a chave vale: key_test_ em operação Teste e key_live_ em operação Real. A API que atende o host só aceita o prefixo do seu tipo; o contrário volta 401 unknown_key (Ambientes, tipos de operação e hosts).

Pedido canônico e assinatura

O pedido canônico tem seis linhas separadas por \n (LF, byte 0x0A), sem \n no final:

v1
<Dracma-Timestamp>
<Dracma-Nonce>
<MÉTODO em maiúsculas>
<caminho + "?" + query exatamente como enviados, ou só o caminho se não houver query>
<sha256 hexadecimal minúsculo do corpo cru; corpo vazio = sha256 de "">

A assinatura é o HMAC-SHA256 do pedido canônico, com o segredo da chave:

assinatura = hex_minúsculo( HMAC-SHA256( segredo, pedido_canonico ) )
Dracma-Signature: v1=<assinatura>

O segredo é usado como os bytes da string que você recebeu (por exemplo sk_test_vetor_0123456789abcdef, com o prefixo sk_test_), em UTF-8. Não decodifique de hexadecimal nem de base64.

sequenceDiagram participant C as Seu conector participant D as Dracma C->>C: serializa o corpo uma vez, sha256 dos bytes C->>C: timestamp (s), nonce (16 bytes aleatórios), pedido canônico C->>C: HMAC-SHA256 com o segredo da chave C->>D: método, caminho, 4 cabeçalhos, Idempotency-Key (POST), os MESMOS bytes do corpo D->>D: confere formato, janela, chave, assinatura, nonce, escopo D-->>C: resposta (Dracma-Trace-Id em toda resposta)

As cinco armadilhas que mais quebram integração

  1. O caminho inclui /v1. Se a sua base é https://<host>/v1 e você chama /deposits, o caminho assinado é /v1/deposits: o caminho completo que chega ao servidor.
  2. A query vai como enviada. Não reordene parâmetros, não decodifique %XX, não normalize. Monte a URL uma vez e assine a mesma string que o cliente HTTP envia (/v1/deposits?external_id=dep_x&limit=10). Desligue qualquer normalização de caminho do seu cliente (curl --path-as-is).
  3. Assine os bytes que você envia. Serialize o JSON uma vez, calcule o sha256 sobre esses bytes e mande esses mesmos bytes. Reserializar (outra ordem de chaves, outro espaçamento, outro escape de Unicode) muda o hash.
  4. Nonce hexadecimal minúsculo, de gerador criptográfico. crypto/rand, crypto.randomBytes, random_bytes, openssl rand -hex 16. Nunca Math.random() nem math/rand. Um nonce com letra maiúscula é recusado.
  5. Timestamp em segundos. Date.now() devolve milissegundos: divida por 1000 e trunque.

Janela de tempo, nonce e anti-replay

  • Janela de ±300 s. abs(agora do servidor − Dracma-Timestamp) precisa ser no máximo 300 s, comparado em segundos inteiros.
  • Relógio. Mantenha NTP no host do conector. GET /v1/ping devolve server_time; compare com o seu relógio e alerte se o desvio passar de 5 s. Um relógio que deriva vira, um dia, 100% de timestamp_out_of_window.
  • Nonce. O Dracma guarda (key_id, nonce) por 600 s e recusa o par repetido com 401 replayed_nonce. Não existe teto de nonces por chave: quem limita o volume é o limite de taxa.
  • Quando o nonce é consumido. Só depois de a assinatura conferir e de a chave estar utilizável. Uma requisição recusada antes disso (janela, assinatura, chave revogada) não queima o nonce.
  • Repetir uma chamada é uma chamada nova. Mesmo depois de um 5xx ou de um timeout, a nova tentativa leva novo Dracma-Timestamp, novo nonce e nova assinatura, e a mesma Idempotency-Key. Reusar o nonce dá replayed_nonce. Passada a verificação de nonce, qualquer recusa seguinte (429, 403, 400) também o consome.

Exemplo trabalhado (vetor normativo 1)

Os valores abaixo vêm de vetores-assinatura.json. São valores de teste públicos; toda implementação deve reproduzi-los byte a byte.

Entrada Valor
segredo sk_test_vetor_0123456789abcdef
key_id key_test_vetor
timestamp 1790000000
nonce 0123456789abcdef0123456789abcdef
método POST
caminho /v1/deposits
corpo {"external_id":"dep_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6b","player":{"external_ref":"u-1","document":{"type":"CPF","number":"12345678909","country":"BR"}},"amount":{"amount":"5000","currency":"BRL"},"method":"pix"}

Passo 1, sha256 do corpo cru:

fed01c42dbe44bee43439f86a259262975544b122ac57e75977fb5520bbb6edb

Passo 2, pedido canônico (seis linhas, sem LF final):

v1
1790000000
0123456789abcdef0123456789abcdef
POST
/v1/deposits
fed01c42dbe44bee43439f86a259262975544b122ac57e75977fb5520bbb6edb

Passo 3, HMAC-SHA256 com o segredo:

Dracma-Signature: v1=a58fa1956941157aad72536512b2b09f234de3670050c865071ddc56df3b4964

Conferência em uma linha de shell:

printf 'v1\n1790000000\n0123456789abcdef0123456789abcdef\nPOST\n/v1/deposits\nfed01c42dbe44bee43439f86a259262975544b122ac57e75977fb5520bbb6edb' \
  | openssl dgst -sha256 -hmac 'sk_test_vetor_0123456789abcdef'
# SHA2-256(stdin)= a58fa1956941157aad72536512b2b09f234de3670050c865071ddc56df3b4964

Os outros dois vetores cobrem os casos de borda:

Vetor Método e caminho Corpo O que prova Assinatura
2 GET /v1/deposits?external_id=dep_x&limit=10 vazio (sha256 de "", começa com e3b0c442) a query entra como enviada; corpo vazio também é assinado v1=83b5e8b97bccfc97db1fdaa7f847b2a229a0835b88ce95032ab7c8f951487316
3 POST /v1/withdrawals/wdr_01J9ZQ4/cancel vazio POST sem corpo também assina o sha256 de "" v1=64df5c1a75e4cfc367e165f55be0d6b50678848069f96abb27a4f2559abdae5d

O arquivo vetores-assinatura-ts-negativos.json traz timestamps fora da forma canônica (+1790000000, 01790000000, com espaço, em notação científica) que o Dracma recusa todos com 401 timestamp_out_of_window. Os clientes de Exemplos (Go, Node/TypeScript, PHP e shell) têm testes que reproduzem os vetores.

Pseudocódigo

função chamar(método, caminho_com_query, corpo_bytes, idempotency_key?):
    ts     = str(floor(agora_unix_segundos()))
    nonce  = hex_minusculo(csprng(16))
    canon  = join("\n", ["v1", ts, nonce, upper(método), "/v1" + caminho_com_query, sha256_hex(corpo_bytes)])
    sig    = "v1=" + hex(hmac_sha256(segredo, canon))
    headers = {Dracma-Key-Id: key_id, Dracma-Timestamp: ts, Dracma-Nonce: nonce, Dracma-Signature: sig}
    se método == POST: headers[Idempotency-Key] = idempotency_key
    envia EXATAMENTE corpo_bytes para base_host + "/v1" + caminho_com_query, com timeout de 10 s

Ordem de verificação

O servidor confere nesta ordem, e o primeiro problema encontrado é o que volta. Isso ajuda a diagnosticar: um 403 significa que a autenticação já passou.

# Verificação Falha
1 Corpo de até 64 KiB 413 validation_failed (errors[0].reason = too_large)
2 Limite de taxa por IP de origem, antes de autenticar 429 rate_limited + Retry-After
3 Dracma-Key-Id bem formado e com o prefixo do tipo de operação deste host. Decidido só pelo cabeçalho, sem consultar a chave 401 unknown_key
4 Dracma-Timestamp na forma canônica e dentro de ±300 s 401 timestamp_out_of_window
5 Dracma-Nonce no formato (32 hexadecimais minúsculos) 401 invalid_signature
6 A chave existe e a assinatura confere. Chave inexistente e assinatura errada dão a mesma resposta 401 invalid_signature; depois de 20 falhas por minuto do mesmo IP, 429 rate_limited
7 Só com assinatura válida: a chave está ativa (não revogada) 401 unknown_key
8 Só com assinatura válida: IP de origem na lista da operação 403 ip_not_allowed (a lista não é aplicada hoje)
9 Só então o nonce é gravado; o par (key_id, nonce) já visto nos últimos 600 s 401 replayed_nonce
10 Operação ativa 403 operation_suspended
11 Limite de taxa por chave e por operação (e o de listagem) 429 rate_limited + Retry-After
12 O escopo da chave cobre a rota 403 insufficient_scope
13 Idempotency-Key válida em todo POST 400 validation_failed (field: Idempotency-Key, reason: required)
14 Operação Teste com a queda simulada ligada (/sandbox/outage), em rota fora de /sandbox/* 503 service_unavailable + Retry-After: 5

Depois disso vêm a validação do corpo (400) e as regras da rota.

  • Sem oráculo de existência. Quem não prova a posse do segredo não descobre se uma chave existe nem se foi revogada. 401 unknown_key por chave revogada só aparece depois de a assinatura de uma chave real conferir.
  • Cabeçalho ausente. Dracma-Key-Id ausente vira unknown_key; timestamp ausente vira timestamp_out_of_window; nonce ou assinatura ausentes viram invalid_signature.
  • O erro nunca traz a assinatura esperada nem o pedido canônico calculado pelo servidor. Para depurar, compare o seu canônico com os vetores.
  • Dependência fora. Se o armazenamento de nonces, o cofre de segredos ou o limitador estiverem fora, o servidor responde 503 service_unavailable com Retry-After: 1. Nunca aceita "sem checar". Repita com backoff.
  • Rota inexistente. A autenticação vem antes do roteamento: uma rota errada com assinatura errada devolve 401, não 404.

Escopos

Cada chave tem de um a quatro escopos. A regra é deny-by-default: método ou rota não mapeados voltam 403 insufficient_scope.

Rota Escopo exigido
GET /v1/ping nenhum (qualquer chave válida)
Qualquer outro GET ou HEAD, inclusive GET /sandbox/webhooks read
POST /deposits, POST /deposits/{id}/cancel deposits:write
POST /sandbox/deposits/{id}/pay, …/expire, …/refund, …/chargeback e POST /sandbox/bank-transfers/incoming deposits:write
POST /withdrawals, POST /withdrawals/{id}/cancel, POST /withdrawals/{id}/recall withdrawals:write
POST /sandbox/withdrawals/{id}/{ação} withdrawals:write
POST /rules/change-requests rules:propose
POST /fx/quotes, POST /sandbox/outage, POST /sandbox/webhooks/redeliver deposits:write ou withdrawals:write

O painel mostra os escopos com rótulos em português. A criação de chave marca read por padrão:

Rótulo no painel Escopo
Ler dados read
Criar depósitos deposits:write
Pedir saques withdrawals:write
Propor mudança de regra rules:propose

Escopos mínimos por componente. Duas chaves com escopos menores são melhores que uma chave com tudo:

Componente do conector Escopos
Serviço que cria e cancela depósitos deposits:write e read (o fetch-back é GET)
Serviço que pede saques e paga withdrawals:write e read
Job de eventos, conciliação e saldos read
Ferramenta interna de propor regras rules:propose (e read, para o GET /rules que dá o ETag)

A criação, a revelação do segredo (uma única vez), a revogação e a troca de chaves são feitas no painel, com aprovação de outra pessoa: veja Chaves de API. Uma operação tem no máximo 2 chaves ativas, o que permite trocar sem parada: crie a nova, faça o deploy do conector, confira o "último uso" no painel e revogue a antiga. A revogação vale em até 60 s em todos os nós.

Exemplos

Ping assinado, com curl e as funções de exemplos/shell/lib.sh. O host é o da sua operação (Ambientes, tipos de operação e hosts):

source exemplos/shell/lib.sh
ts="$(date +%s)"; nonce="$(openssl rand -hex 16)"
sig="$(dracma_assinar "$(cat "$DRACMA_KEY_SECRET_FILE")" "$ts" "$nonce" GET /v1/ping)"
curl -sS --path-as-is --max-time 10 \
  -H "Dracma-Key-Id: $DRACMA_KEY_ID" -H "Dracma-Timestamp: $ts" \
  -H "Dracma-Nonce: $nonce" -H "Dracma-Signature: $sig" \
  "$DRACMA_BASE_URL/ping"

DRACMA_BASE_URL termina em /v1. O segredo fica em arquivo com permissão 0400, fora do repositório; o lib.sh é para depurar, e em produção você usa a biblioteca do seu cliente.

Erros e casos de borda

Resposta Causa mais provável O que fazer
401 invalid_signature com o canônico igual ao dos vetores Dracma-Key-Id digitado errado, segredo de outra chave, segredo com espaço ou quebra de linha no fim Releia o segredo do cofre sem \n; confira o key_id
401 invalid_signature só em POST Corpo reserializado depois de assinado, ou Content-Length diferente do corpo assinado Assine e envie os mesmos bytes
401 invalid_signature só com query Query reordenada ou decodificada pelo cliente HTTP Assine a URL exatamente como enviada
401 timestamp_out_of_window Relógio fora de ±300 s; timestamp em milissegundos; com sinal, espaço ou zero à esquerda NTP; floor(agora / 1000)
401 replayed_nonce Nonce reutilizado, inclusive em retry Gere nonce novo a cada tentativa
401 unknown_key Prefixo key_test_ ou key_live_ do outro tipo de operação; chave revogada; key_id mal formado Confira a chave e o host
403 insufficient_scope Chave sem o escopo da rota; método fora de GET, HEAD, POST Use a chave do componente certo
403 operation_suspended Operação suspensa pelo Dracma Perguntas frequentes
429 rate_limited Limite de taxa ou 20 falhas de assinatura por minuto do mesmo IP Limites e quotas
503 service_unavailable Dependência do Dracma fora (nonces, segredos, limitador) Espere Retry-After e repita

Roteiro de diagnóstico completo, com os passos na ordem, em Problemas de integração.

Veja também