Descriptor do provedor¶
Para: Dev · Adm — Conferido em 2026-10-31.
O descriptor é um documento JSON que todo provedor de liquidação serve em GET /spp/v1/descriptor. Ele é a fonte única de tudo o que varia de um provedor para outro: o que ele sabe fazer, quais campos a credencial tem, para onde ele fala, em que ambiente pode rodar, como o parceiro assina callbacks e quais limites o parceiro impõe. O núcleo do Dracma (executor, api, painel, ops) não guarda lista de provedores nem compara nome de provedor: ele lê o descriptor.
Esta página é a referência campo a campo. O protocolo em si (rotas, erros, HMAC) está no contrato SPP v1; aqui o foco é o documento que você escreve para plugar um provedor.
O que é¶
- Um objeto JSON com 21 campos de primeiro nível (17 obrigatórios, 4 opcionais).
- Servido por
GET /spp/v1/descriptor, autenticado por HMAC interno v2 (sem credencial de tenant no corpo, porque o descriptor não depende de tenant). - Copiado pelo executor no registro (
register) junto com seu hash (descriptor_sha256). Mudar o descriptor muda o hash e dispara as regras da seção Regras de mudança do descriptor. - Validado pelo registro contra o schema
Descriptordo contrato (additionalProperties: false): campo desconhecido no nível raiz é erro de registro.
Quando usar¶
- Você vai escrever um provedor novo (nosso ou de terceiro): o descriptor é a primeira coisa a existir, antes de qualquer rota de negócio.
- Você vai operar um provedor: o descriptor diz quais hosts liberar no egress, quais IPs de callback conferir e qual política de ambiente vale.
- Você vai mudar um provedor já plugado (nova capacidade, novo host, novo campo de credencial): as regras de compatibilidade decidem se o provedor continua
enabledou volta aregistered.
Como funciona¶
Quem lê o quê:
| Consumidor | Campos que lê | O que deriva |
|---|---|---|
| Painel e admin | display_name, credential_schema, config_schema, capabilities, links, deprecations |
Formulário de nova conexão, rótulos, ajuda, campos mascarados, avisos de depreciação |
| api | capabilities, credential_schema, config_schema, limits |
Validação de credencial e config na criação da conexão, matriz de direção e método (roteamento), GET /currencies de cripto |
| executor | environments, environment_policy, credential_mode, callbacks, idempotency, rate_limits, timeouts, quirks, spp_versions_supported |
Guarda de ambiente, escolha de base_url, conferência de IP e assinatura de callback, balde de rate limit, prazos, negociação de versão |
| ops | name, egress_hosts, callbacks.source_ips |
Regra de saída por UID, rota de callback, par HMAC INTERNAL_HMAC_<PROVEDOR>_EXECUTOR, métricas e rótulos |
Campos do descriptor¶
Legenda: Obr. = obrigatório no schema Descriptor. Tipos seguem JSON; MAJOR.MINOR é a expressão ^[0-9]+\.[0-9]+$.
| Campo | Tipo | Obr. | Regras |
|---|---|---|---|
name |
string ^[a-z0-9_-]{1,64}$ |
sim | Identificador opaco e imutável. Vira o {provider} da rota de callback, o rótulo psp das métricas, o sufixo do segredo HMAC (maiúsculas, - vira _) e o valor gravado no registro. |
display_name |
string, até 80 caracteres | sim | Rótulo mostrado no painel. |
version |
string (semver) | sim | Versão do adaptador (não do protocolo). |
spp_version |
MAJOR.MINOR |
sim | A versão do SPP que o provedor fala melhor. Hoje 1.0. |
spp_versions_supported |
array de MAJOR.MINOR, mínimo 1 item |
sim | Todas as versões que ele aceita. O executor escolhe a maior versão comum. |
synthetic |
boolean | sim | true = dinheiro de mentira (simulador). Recusado em prd e em operação production. |
capabilities |
objeto | sim | Veja Capacidades. |
quirks |
objeto | sim | not_found_is_definitive (boolean, default false) e payout_return_hypothetical (informativo). |
credential_schema |
JSON Schema 2020-12 | sim | Campos da credencial do tenant. Marcadores x-secret, x-label, x-help. Use additionalProperties: false. |
credential_mode |
objeto {field, test_value} |
não | Campo da credencial que declara "credencial de teste". Ausente quando o parceiro não distingue. |
config_schema |
JSON Schema 2020-12 | sim | Configuração não secreta por conexão (base_url e semelhantes). |
environments |
objeto {sandbox?, live} |
sim | Cada ambiente é {base_url, doc_url?}. Sem sandbox, o parceiro não tem ambiente de teste. live é obrigatório, exceto em synthetic. |
environment_policy |
objeto {live_outside_prd} |
sim | forbidden ou flag. Veja Ambientes e política de ambiente. |
egress_hosts |
array de host:porta, itens únicos |
sim | Única lista de saída que o ops libera. Padrão ^[a-z0-9.-]+:[0-9]{1,5}$. Deve incluir o host de todos os environments. |
callbacks |
objeto {signature, source_ips} |
sim | Assinatura do callback do parceiro (ou null) e IPs de origem. |
idempotency |
objeto | sim | Como o provedor deduplica. Veja Idempotência. |
rate_limits |
objeto | sim | Limites observados no parceiro. |
timeouts |
objeto | sim | Prazos por operação. |
limits |
objeto {min_amount, max_amount, currency} |
não | Faixa de valor do parceiro. Informativo: a decisão de valor é das regras do núcleo. |
deprecations |
array | não | Recursos em depreciação. Veja Depreciação. |
links |
objeto {docs_url, support} |
não | Documentação e contato de suporte do provedor. |
Regras transversais:
- O objeto raiz não aceita campo extra (
additionalProperties: false). Dentro dequirks,limitselinks, o leitor do núcleo é tolerante e ignora o que não conhece. - O descriptor é servido sem credencial. Nunca coloque segredo nele, nem exemplo de segredo real.
namenunca muda. Para "renomear", registre um provedor novo e mova o roteamento.- Dinheiro em
limitsé string inteira na menor unidade ("1000"= R$ 10,00 emBRL).
Capacidades¶
capabilities é o contrato de "o que este provedor faz". A regra geral do SPP: capacidade true significa que a operação deve funcionar e será coberta pela certificação; capacidade false significa que a rota responde 501 unsupported com effect: none. O núcleo nunca infere capacidade pelo nome do provedor.
Forma completa:
{
"directions": ["cash_in", "cash_out"],
"methods": [
{ "method": "pix", "directions": ["cash_in", "cash_out"] }
],
"balance": false,
"charge_lookup": true,
"charge_ref_resolution": false,
"charge_cancel": false,
"credential_check": true,
"key_lookup": { "dict": false, "key_types": [] },
"fee_reporting": "none",
"refunds": { "payout_returns": false, "charge_refund": false },
"callbacks": true,
"event_delivery": "callback"
}
| Capacidade | Valores | Rota SPP ligada | Se true, a certificação exige |
|---|---|---|---|
directions |
cash_in, cash_out |
POST /charges (entrada), POST /payouts e POST /payouts/get (saída) |
Entrada: cobrança idempotente (mesma chave devolve a mesma cobrança, com Spp-Replayed: true; corpo diferente com a mesma chave dá 409 idempotency_conflict). Saída: saque idempotente, falha de rede depois do envio vira upstream_timeout com effect: unknown, e payouts/get responde para toda chave já aceita (ou not_found). |
methods |
lista de {method, directions, assets?} com method em pix ou crypto |
corpo de POST /charges (campo method) |
O pedido de cada método declarado é aceito; método não declarado é recusado. assets só é permitido com method: crypto. |
balance |
boolean | POST /account/balance |
Resposta available/blocked/currency; formato irreconhecível dá 502 unrecognized_response, nunca saldo zero. Se false: 501 unsupported. |
charge_lookup |
boolean | POST /charges/get |
Estados pending, paid, expired, observed, unrecognized; em paid, valor e pagador reais (fetch-back). Se false: 501 unsupported (caso atual da MaximaxPay). |
charge_ref_resolution |
boolean | POST /charges/resolve-ref |
Referência do parceiro vira deposit_id; pagamento que não é do Dracma devolve 200 com deposit_id: null, sem erro. |
charge_cancel |
boolean | POST /charges/cancel |
Idempotente (cancelled ou already_terminal). Exigida de provedor cripto (expiração de cobrança). |
credential_check |
boolean | POST /credentials/check |
Faz só leitura inócua no parceiro; credencial recusada pelo parceiro é 200 com valid: false, não erro. Falha de infraestrutura é erro. |
key_lookup |
{dict: boolean, key_types: [..]} |
POST /keys/lookup |
Documento completo devolvido e nunca registrado; chave inexistente dá 404 key_not_found com effect: none. |
fee_reporting |
none, on_create, on_query, both |
campo fee de payouts e payouts/get |
fee sempre presente na resposta (ok, absent ou unrecognized); tarifa ilegível nunca vira erro de envio; ok só com 0 <= fee <= amount. |
refunds.payout_returns |
boolean | estado returned em payouts/get |
Pagamento devolvido pelo banco do recebedor aparece como returned (valor, motivo, data). Sem esta capacidade o provedor não pode emitir returned. |
refunds.charge_refund |
boolean | nenhuma na v1 | Deve ser false. O campo reserva lugar para uma operação aditiva (versão MINOR). |
callbacks |
boolean | POST /callbacks/verify |
Verificação de assinatura em tempo constante sobre o corpo cru (body_base64); assinatura inválida dá 422 callback_unauthenticated sem revelar o motivo; resposta ao teste com credencial isca idêntica à de conexão real. Se true, callbacks.signature do descriptor deve estar preenchido. |
event_delivery |
callback, bus, none |
fora das rotas | callback: progresso chega por callback do parceiro. bus: o provedor publica eventos no barramento (caso cripto). none: só consulta. |
Combinações incoerentes (a certificação reprova e o registro não deve aceitar):
directionscontémcash_outmas a rota/payoutsresponde501. Vale o inverso paracash_ine/charges.callbacks: truecomcallbacks.signature: null.methodscomassetsnum método que não sejacrypto.charge_cancel: falsenum provedor cujo método écrypto.refunds.charge_refund: true(a v1 não tem consumidor).
Matriz de capacidade derivada
A matriz de direção por provedor (que antes estava copiada no roteamento do executor, no painel, na api e no cofre de credenciais) passa a ser calculada de directions e methods. Se a sua capacidade está errada no descriptor, o roteamento fica errado em todos os lugares ao mesmo tempo.
credential_schema e config_schema¶
Os dois são JSON Schema 2020-12. A diferença é de natureza:
credential_schema |
config_schema |
|
|---|---|---|
| Conteúdo | Segredos e identificadores de acesso do tenant no parceiro | Configuração não secreta da conexão |
| Onde vive | Cifrada no cofre do executor; vai no corpo de cada chamada (credential) |
Em claro na conexão; vai no corpo de cada chamada (config) |
| Aparece em resposta de API? | Nunca (campos x-secret são só de escrita) |
Sim |
| Exemplos | access_token, client_secret, webhook_secret |
base_url, xpubs, node_urls |
Marcadores¶
| Marcador | Onde | Efeito |
|---|---|---|
x-secret: true |
em uma propriedade | O painel desenha campo mascarado e de escrita única (nunca reexibido); a api e o executor tratam o valor como segredo: não logam, não devolvem, redigem no Debug. Todo campo que dá acesso ou assina algo deve ter. |
x-label |
em uma propriedade | Rótulo do campo no painel, em pt-BR. |
x-help |
em uma propriedade | Texto de ajuda curto sob o campo (onde achar o valor no painel do parceiro). |
additionalProperties: false |
no objeto raiz | Campo desconhecido no pedido vira 422 misconfigured com field indicando o campo. |
Os demais campos são JSON Schema comum (type, required, minLength, maxLength, pattern, format, default, enum) e valem como validação.
Exemplo mínimo de uma propriedade:
{
"access_token": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"x-secret": true,
"x-label": "Access token",
"x-help": "Credenciais de produção do aplicativo, no painel do parceiro."
}
}
Como painel, api e ops derivam as coisas¶
| Derivação | Fonte no descriptor | Resultado |
|---|---|---|
| Formulário do painel | credential_schema.properties, required, x-label, x-help, x-secret, type |
Um campo por propriedade, com o tipo de controle escolhido pelo type (um boolean vira caixa de seleção); x-secret vira campo mascarado de escrita única. |
| Validação na api | credential_schema e config_schema |
Corpo de criação/rotação de conexão validado contra o schema antes de gravar; campo fora do schema é recusado. |
| Validação no provedor | os mesmos schemas | Toda chamada de negócio é validada de novo (422 misconfigured com field). Não confie só na api. |
| Egress | egress_hosts |
Regra de saída por UID do provedor: só esses host:porta, mais DNS e telemetria local. Veja a página de segurança do provedor. |
| Ingress | name + registro |
A rota /callbacks/{provider}/{connection_id} do ingress aceita qualquer provedor registrado e habilitado e encaminha ao executor. Nenhum provedor novo exige editar rota. |
| IPs de callback | callbacks.source_ips[mode] |
O executor confere o IP de origem antes de chamar callbacks/verify; lista vazia não restringe. |
| Guarda de ambiente | synthetic, environments, environment_policy, credential_mode |
Tabela da seção a seguir. |
base_url da conexão |
environments[mode].base_url ou valor de config cujo host esteja em egress_hosts |
Host fora da lista dá 422 misconfigured (anti-SSRF). |
Ambientes e política de ambiente¶
Dois conceitos que não se confundem:
- modo:
sandbox(ambiente de teste do parceiro) oulive(dinheiro real). Quem decide é o executor; o provedor confirma. - ambiente de execução:
dev,hmlouprd. É fail-closed: ausência de valor é tratada como não-prd.
environments:
{
"sandbox": { "base_url": "https://sandbox.parceiro.example", "doc_url": "https://docs.parceiro.example/sandbox" },
"live": { "base_url": "https://api.parceiro.example" }
}
environment_policy.live_outside_prd aceita dois valores: forbidden (padrão seguro: modo live fora de prd é recusado) e flag (exceção controlada: só com a chave PSP_REAL_OUTSIDE_PRD contendo o name, em hml e em operação production, com a mesma auditoria da ADR-0026).
Tabela da guarda¶
| Provedor | Ambiente | Modo permitido | Regra |
|---|---|---|---|
synthetic = true |
dev, hml |
sandbox |
Recusado em prd e em operação production (como o simulador hoje). |
synthetic = false |
prd |
live |
sandbox em prd é recusado. Credencial de teste é recusada (credential_mode). |
synthetic = false, com environments.sandbox |
dev, hml |
sandbox |
Usa só a URL de sandbox; a credencial deve declarar teste (credential_mode). |
synthetic = false, sem sandbox |
dev, hml |
live (exceção) |
Exige live_outside_prd = flag e PSP_REAL_OUTSIDE_PRD contendo o name e ambiente hml e operação production. Senão, erro de guarda de ambiente. |
Regras adicionais:
credential_modecom{"field": "test_credential", "test_value": true}: emsandboxa credencial precisa tertest_credential == true; emliveprecisa terfalse. O provedor pode recusar também por padrão do token (por exemplo o prefixoTEST-do Mercado Pago), respondendo403 environment_forbidden.- Fora de
prd, o executor nunca usaenvironments.live.base_url, salvo a exceção da última linha. - Defesa em profundidade: o provedor reaplica a guarda. Ele recusa
mode: livecomruntime_envdiferente deprd, salvo seenvironment_policypermitir e o executor sinalizarcontext.live_exception: true. Recusa tambémmode: sandboxcomruntime_env == prd. A resposta é403 environment_forbiddencomeffect: none. - A chave
PSP_REAL_OUTSIDE_PRD=<nome>[,<nome>]vive no seeder da aplicação, só é escrita pelo ops (psp-real) e substitui as duas chaves antigas por provedor.
Detalhes de segurança e de efeito externo estão em Segurança do provedor.
Callbacks¶
callbacks descreve como o parceiro assina o que manda para o Dracma e de onde ele manda:
{
"signature": {
"scheme": "hmac-sha256-hex-prefixed",
"header": "x-signature",
"algorithm": "HMAC-SHA256",
"covers": "raw_body",
"encoding": "sha256=<hex>"
},
"source_ips": { "sandbox": [], "live": ["203.0.113.0/24"] }
}
| Campo | Tipo | Obr. | Regras |
|---|---|---|---|
signature |
objeto ou null |
sim | null quando o parceiro não assina (e então capabilities.callbacks é false). |
signature.scheme |
string | sim | Nome livre do esquema, por exemplo hmac-sha256-hex-prefixed ou hmac-sha256-manifest. |
signature.header |
string | sim | Cabeçalho da assinatura, em minúsculas. É repassado ao provedor em request.headers. |
signature.algorithm |
string | sim | Por exemplo HMAC-SHA256. |
signature.covers |
string | sim | raw_body (bytes crus do corpo) ou manifest (texto montado a partir de campos). |
signature.encoding |
string | não | Formato do valor: sha256=<hex> ou ts=<n>,v1=<hex>. |
source_ips |
objeto {sandbox, live} |
sim | Listas de CIDR por modo. Lista vazia ou ausente não restringe. |
paths_extra |
array | não | Nunca necessário: a rota de callback é fixa (/callbacks/{provider}/{connection_id}). |
Contrato de assinatura no fluxo:
- O parceiro chama
https://<host>/callbacks/{provider}/{connection_id}. - O ingress encaminha ao executor; o executor acha a conexão, confere o IP contra
source_ips[mode], aplica a guarda de ambiente e chamaPOST /spp/v1/callbacks/verifyno provedor, comrequest(método, path, cabeçalhos do descriptor maiscontent-type, query,body_base64,received_at). - O provedor verifica a assinatura sobre os bytes de
body_base64(não reserializa JSON), em tempo constante, e valida janela de tempo quando o parceiro assina timestamp. - Resposta
200 {"events": [...]}ou422 callback_unauthenticated. Ao parceiro o executor responde200,401uniforme (assinatura inválida ou conexão desconhecida) ou503(falha transitória, para o parceiro reenviar). - O evento é só uma afirmação (
claimed). Nenhum callback move dinheiro sozinho: o executor confirma por consulta (fetch-back) antes de qualquer efeito.
Tipos de evento devolvidos por callbacks/verify:
Event.type |
Campos | Sentido |
|---|---|---|
payout |
idempotency_key, claimed (estado do saque) |
Saque mudou de estado |
charge |
deposit_id, claimed (estado da cobrança) |
Cobrança mudou, com o depósito já identificado |
charge_by_ref |
psp_ref |
Só a referência do parceiro veio; o executor chama resolve-ref e charges/get |
ignored |
topic? |
Autêntico, sem efeito |
Conexão desconhecida: o executor chama o provedor com uma credencial isca aleatória no formato do credential_schema, para que a resposta externa não revele se a conexão existe. O provedor não pode distinguir isca de credencial real.
Idempotência¶
{
"charge_key": "deposit_id",
"payout_key": "psp_idempotency_key",
"upstream_dedupe": true,
"retention_hours": 24
}
| Campo | Valores | Regra |
|---|---|---|
charge_key |
deposit_id ou charge_id |
Chave de POST /charges: deposit_id para PIX, charge_id para cripto. Vai em Spp-Idempotency-Key e no corpo; os dois devem coincidir (senão 400 invalid_request). |
payout_key |
psp_idempotency_key ou none |
Chave de POST /payouts. none quando o provedor não faz saque. |
upstream_dedupe |
boolean | true quando o parceiro deduplica pelo identificador que o provedor repassa (forma da MaximaxPay identifier e do Mercado Pago X-Idempotency-Key). false obriga repositório local. |
retention_hours |
inteiro, mínimo 24 | Por quanto tempo o resultado de uma chave é guardado para repetição. |
Comportamento exigido (S7, §8 do contrato):
- Mesma chave e mesmo corpo (desconsiderando
credentialecontext.operation_id): mesmo resultado, comSpp-Replayed: true, sem novo efeito no parceiro. - Mesma chave com corpo diferente:
409 idempotency_conflict(effect: none). - Chamada concorrente com a mesma chave: uma executa; as outras esperam ou recebem
409 idempotency_in_progress(effect: unknown). - Repositório local (quando
upstream_dedupeéfalse): chave para resultado, sem segredo e sem PII além do necessário. payouts/getdeve responder para qualquer chave já aceita porpayouts, ounot_found. É a fonte de verdade para resolver saque ambíguo; o executor nunca reenvia saque às cegas.
Rate limits¶
{ "requests_per_second": 10, "burst": 20, "per": "connection" }
| Campo | Tipo | Regra |
|---|---|---|
requests_per_second |
número, mínimo 0 | Taxa sustentada que o parceiro tolera. |
burst |
inteiro, mínimo 1 | Rajada máxima. |
per |
connection ou provider |
O balde é por conexão (cada tenant tem o seu limite no parceiro) ou por provedor (limite global da conta do parceiro). |
O executor aplica um token bucket no cliente com esses valores. Excesso: espera até o prazo da chamada e depois devolve Unavailable(429) (ou CircuitOpen em create_charge, o que habilita failover seguro de depósito). Declare o limite real do parceiro, não um valor otimista. Separadamente, o próprio provedor pode responder 429 provider_busy com Retry-After quando a sua fila está cheia.
Timeouts¶
{ "default_ms": 15000, "max_ms": 30000, "payout_create_ms": 30000 }
| Campo | Regra |
|---|---|
default_ms |
Mínimo 1000. Prazo padrão por operação. |
max_ms |
Máximo 120000. Teto absoluto. |
payout_create_ms |
Opcional. Eleva o prazo de POST /payouts até max_ms. |
O executor envia o orçamento da chamada em Spp-Timeout-Ms. O provedor deve limitar a chamada ao parceiro a Spp-Timeout-Ms - 500 ms, para sobrar tempo de responder com o erro certo. O executor mantém um cliente HTTP por provedor, connect de até 1 s, sem proxy e sem redirecionamento. Prazo estourado sem resposta é classificado pelo executor como ambíguo (Timeout), nunca como falha definitiva.
Depreciação¶
{
"deprecations": [
{ "feature": "legacy_fee_field", "sunset": "2027-06-30", "replacement": "fee.state" }
]
}
| Campo | Tipo | Obr. | Regra |
|---|---|---|---|
feature |
string | sim | Nome do recurso depreciado. |
sunset |
data YYYY-MM-DD |
sim | A partir desta data o recurso pode ser removido. |
replacement |
string | não | O que usar no lugar. |
- O executor suporta o MAJOR corrente e o anterior por pelo menos 12 meses após o anúncio.
- O provedor pode emitir
DeprecationeSunset(RFC 8594) nas respostas. - O registro mostra o aviso ao operador do painel e do ops.
- A certificação falha para o provedor que ainda usa um recurso cujo
sunsetjá passou.
Regras de mudança do descriptor¶
O registro compara o descriptor_sha256. Quando muda:
| Tipo | Exemplos | Efeito |
|---|---|---|
| Compatível | Capacidade nova; novo item em deprecations; novo links; novo valor de version |
Registrado e auditado; o provedor continua no estado em que está. |
| Incompatível | Capacidade removida; credential_schema mais restrito (campo novo obrigatório, pattern mais estreito, campo removido); egress_hosts alterado; environments alterado |
O provedor volta a registered automaticamente (deixa de receber trabalho novo até novo enable, que exige saúde, egress/ingress conferidos e certificação do novo hash). |
| Proibida | Mudar name |
Recusada. name é imutável. |
Regras específicas:
- Capacidade removida com conexão que a usa: recusa da mudança.
- Qualquer mudança de
egress_hostsouenvironmentsexige que o ops reaplique egress antes doenable(o ops confere). - O contrato não classifica mudança em
quirks,rate_limits,timeoutsouidempotency. Se a mudança altera comportamento financeiro (por exemplonot_found_is_definitivedefalseparatrue), trate como incompatível e certifique de novo. - Versão MINOR do SPP só adiciona (campo opcional, valor novo de capacidade ou de código de erro, operação opcional). Remover, renomear, tornar obrigatório ou mudar semântica é MAJOR, com outro path (
/spp/v2). - Todo
register,enable,drain,disable,unplugecertifygrava quem, quando, motivo,descriptor_sha256etrace_id.
Descriptors completos¶
Os quatro descriptors abaixo descrevem o comportamento atual dos adaptadores. A extração dos adaptadores para processos externos não muda comportamento. Valores marcados como referência (rate limits, timeouts) são os que o adaptador usa hoje ou valores conservadores, porque o parceiro não publica limite confirmado.
simulator¶
Provedor sintético: dinheiro de mentira, só dev e hml. Não tem credencial, não faz chamada externa e não recebe callback. Os roteiros de saque (pay, fail, timeout, pay_and_timeout, manual) existem no adaptador e o centavo 77 aciona o modo manual.
{
"name": "simulator",
"display_name": "Simulador (sandbox)",
"version": "1.0.0",
"spp_version": "1.0",
"spp_versions_supported": ["1.0"],
"synthetic": true,
"capabilities": {
"directions": ["cash_in", "cash_out"],
"methods": [
{ "method": "pix", "directions": ["cash_in", "cash_out"] }
],
"balance": false,
"charge_lookup": true,
"charge_ref_resolution": false,
"charge_cancel": false,
"credential_check": false,
"key_lookup": { "dict": true, "key_types": ["cpf", "cnpj", "email", "phone", "evp"] },
"fee_reporting": "none",
"refunds": { "payout_returns": true, "charge_refund": false },
"callbacks": false,
"event_delivery": "none"
},
"quirks": {
"not_found_is_definitive": true,
"payout_return_hypothetical": false
},
"credential_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"properties": {}
},
"config_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"properties": {}
},
"environments": {
"sandbox": { "base_url": "http://127.0.0.1:7101/spp/v1" }
},
"environment_policy": { "live_outside_prd": "forbidden" },
"egress_hosts": [],
"callbacks": {
"signature": null,
"source_ips": { "sandbox": [], "live": [] }
},
"idempotency": {
"charge_key": "deposit_id",
"payout_key": "psp_idempotency_key",
"upstream_dedupe": false,
"retention_hours": 24
},
"rate_limits": { "requests_per_second": 1000, "burst": 1000, "per": "provider" },
"timeouts": { "default_ms": 15000, "max_ms": 30000 },
"links": { "support": "dev@dracma.invalid" }
}
Notas do simulador:
egress_hostsvazio: o usuário do simulador não tem saída de rede nenhuma (só loopback e telemetria local).synthetic: truedispensaliveemenvironments.payout_returns: trueporque o adaptador sabe responderreturned(RETURNED, motivosim_returned). O motivo de falha (sim_rejected) respeita[a-z0-9_]{1,40}.- Consulta de cobrança inexistente responde recusa de negócio (
404), que o protocolo traduz paraupstream_rejectedcomupstream_status: 404.
maximaxpay¶
PIX de entrada e saída. Credencial client_id, client_secret e webhook_secret. Callback assinado por HMAC-SHA256 sobre o corpo cru, no cabeçalho x-signature com o valor sha256=<hex>. Não há ambiente de teste do parceiro: por isso só existe live, e environment_policy é flag (exceção controlada de hml, ADR-0026).
{
"name": "maximaxpay",
"display_name": "MaximaxPay (PIX)",
"version": "1.0.0",
"spp_version": "1.0",
"spp_versions_supported": ["1.0"],
"synthetic": false,
"capabilities": {
"directions": ["cash_in", "cash_out"],
"methods": [
{ "method": "pix", "directions": ["cash_in", "cash_out"] }
],
"balance": true,
"charge_lookup": false,
"charge_ref_resolution": false,
"charge_cancel": false,
"credential_check": true,
"key_lookup": { "dict": false, "key_types": [] },
"fee_reporting": "both",
"refunds": { "payout_returns": false, "charge_refund": false },
"callbacks": true,
"event_delivery": "callback"
},
"quirks": {
"not_found_is_definitive": false,
"payout_return_hypothetical": true
},
"credential_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["client_id", "client_secret", "webhook_secret"],
"properties": {
"client_id": {
"type": "string",
"minLength": 1,
"maxLength": 256,
"x-label": "Client ID",
"x-help": "Identificador da integração no painel da MaximaxPay. Vai no cabeçalho x-client-id."
},
"client_secret": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"x-secret": true,
"x-label": "Client secret",
"x-help": "Segredo da integração. Vai no cabeçalho x-client-secret e nunca é exibido de novo."
},
"webhook_secret": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"x-secret": true,
"x-label": "Segredo do webhook",
"x-help": "Segredo usado pela MaximaxPay para assinar os callbacks (cabeçalho x-signature)."
}
}
},
"config_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["base_url"],
"properties": {
"base_url": {
"type": "string",
"format": "uri",
"pattern": "^https://",
"default": "https://api.maximaxpay.com",
"x-label": "URL base da API",
"x-help": "O host precisa estar em egress_hosts do descriptor."
}
}
},
"environments": {
"live": { "base_url": "https://api.maximaxpay.com" }
},
"environment_policy": { "live_outside_prd": "flag" },
"egress_hosts": ["api.maximaxpay.com:443"],
"callbacks": {
"signature": {
"scheme": "hmac-sha256-hex-prefixed",
"header": "x-signature",
"algorithm": "HMAC-SHA256",
"covers": "raw_body",
"encoding": "sha256=<hex>"
},
"source_ips": { "sandbox": [], "live": [] }
},
"idempotency": {
"charge_key": "deposit_id",
"payout_key": "psp_idempotency_key",
"upstream_dedupe": true,
"retention_hours": 24
},
"rate_limits": { "requests_per_second": 5, "burst": 10, "per": "connection" },
"timeouts": { "default_ms": 15000, "max_ms": 30000, "payout_create_ms": 30000 },
"links": { "support": "suporte@parceiro.example" }
}
Notas da MaximaxPay (todas vêm do adaptador):
- Idempotência pelo parceiro: o campo
identifierda MaximaxPay recebedeposit_idna cobrança epsp_idempotency_keyno saque. Por issoupstream_dedupeétrue. Na cobrança,psp_refé o própriodeposit_id. - Sem consulta de cobrança (lacuna L1):
charge_lookup: false, entãoPOST /charges/getePOST /charges/resolve-refrespondem501 unsupported. - Consulta de saque por
GET /gateway/transfers?clientIdentifier=<chave>. Chave desconhecida dánot_found, não definitivo (not_found_is_definitive: false): silêncio do parceiro não prova que o saque não saiu. - Devolução (
returned): o formato do parceiro ainda é hipotético. O adaptador recusa o estado e devolveunrecognized, por issopayout_returns: falseepayout_return_hypothetical: true. - Tarifa: lida na criação e na consulta; ilegível vira
unrecognized, nunca erro de envio. - Saldo:
GET /gateway/account/balance, formato desconhecido viraunrecognized_response, nunca zero.credential_checké uma leitura de saldo. - Nome do titular:
owner.nameé normalizado pelo adaptador eowner.ip(IPv4 do jogador) é obrigatório no saque. - Callback: a assinatura é obrigatória no Dracma (a documentação do parceiro a chama de opcional). O corpo é interpretado como cobrança quando
identifieré UUID, e como saque no restante. - IPs de callback: não publicados pelo parceiro; as listas ficam vazias (não restringem) até que ele informe.
base_urlpor conexão: obrigatório (HTTPS fora de dev). O padrão éhttps://api.maximaxpay.com.
mercadopago¶
Só entrada PIX (cash_in). O Mercado Pago não tem API pública de saque PIX para chave de terceiro: POST /payouts e POST /payouts/get respondem 501 unsupported sem chamar o parceiro. Credencial access_token, webhook_secret e test_credential. O callback é assinado sobre um manifesto, não sobre o corpo.
{
"name": "mercadopago",
"display_name": "Mercado Pago (PIX)",
"version": "1.0.0",
"spp_version": "1.0",
"spp_versions_supported": ["1.0"],
"synthetic": false,
"capabilities": {
"directions": ["cash_in"],
"methods": [
{ "method": "pix", "directions": ["cash_in"] }
],
"balance": false,
"charge_lookup": true,
"charge_ref_resolution": true,
"charge_cancel": false,
"credential_check": false,
"key_lookup": { "dict": false, "key_types": [] },
"fee_reporting": "none",
"refunds": { "payout_returns": false, "charge_refund": false },
"callbacks": true,
"event_delivery": "callback"
},
"quirks": {
"not_found_is_definitive": false,
"payout_return_hypothetical": false
},
"credential_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["access_token", "webhook_secret"],
"properties": {
"access_token": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"x-secret": true,
"x-label": "Access token",
"x-help": "Token de acesso do aplicativo (Authorization: Bearer). Em teste, o prefixo costuma ser TEST-."
},
"webhook_secret": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"x-secret": true,
"x-label": "Assinatura secreta do webhook",
"x-help": "Painel Suas integrações, seção Webhooks. Usada para validar o cabeçalho x-signature."
},
"test_credential": {
"type": "boolean",
"default": false,
"x-label": "Credencial de teste",
"x-help": "Marque somente se for uma credencial de teste do Mercado Pago. Credencial de teste é recusada em produção."
}
}
},
"credential_mode": { "field": "test_credential", "test_value": true },
"config_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"properties": {
"base_url": {
"type": "string",
"format": "uri",
"pattern": "^https://",
"default": "https://api.mercadopago.com",
"x-label": "URL base da API",
"x-help": "Opcional. Omitido, usa https://api.mercadopago.com."
}
}
},
"environments": {
"sandbox": { "base_url": "https://api.mercadopago.com" },
"live": { "base_url": "https://api.mercadopago.com" }
},
"environment_policy": { "live_outside_prd": "forbidden" },
"egress_hosts": ["api.mercadopago.com:443"],
"callbacks": {
"signature": {
"scheme": "hmac-sha256-manifest",
"header": "x-signature",
"algorithm": "HMAC-SHA256",
"covers": "manifest",
"encoding": "ts=<n>,v1=<hex>"
},
"source_ips": { "sandbox": [], "live": [] }
},
"idempotency": {
"charge_key": "deposit_id",
"payout_key": "none",
"upstream_dedupe": true,
"retention_hours": 24
},
"rate_limits": { "requests_per_second": 10, "burst": 20, "per": "connection" },
"timeouts": { "default_ms": 15000, "max_ms": 30000 },
"links": { "support": "suporte@parceiro.example" }
}
Notas do Mercado Pago (todas vêm do adaptador):
- Mesmo host nos dois modos: o Mercado Pago distingue teste de produção pela credencial, não pelo host. Por isso
sandboxeliveapontam para o mesmobase_url, e é ocredential_modeque separa os dois:sandboxexigetest_credential: true,liveexigefalse. O adaptador ainda conferelive_modeem cada resposta do parceiro e recusa divergência comomisconfigured. Emlive, também recusa token com prefixoTEST-. - Manifesto assinado: o cabeçalho é
x-signature: ts=<ts>,v1=<hex>ev1é o HMAC-SHA256 do textoid:<data.id em minúsculas>;request-id:<x-request-id>;ts:<ts>;(a parterequest-idé omitida quando o cabeçalho não veio). Odata.idvem da query (data.id=...). A assinatura não cobre o corpo: o corpo só é conferido contra odata.idassinado e todo efeito vem da consulta. - Cabeçalhos repassados em
callbacks/verify:x-signatureex-request-id; a query chega emrequest.query. - Janela anti-replay: o adaptador aceita
tsem segundos ou milissegundos e rejeita fora de 600 s. - Eventos: callback com tópico diferente de
paymentviraignored; o restante viracharge_by_refcom odata.id, e o executor confirma comresolve-refecharges/get. - Consulta:
charge_lookupusaGET /v1/payments/search?external_reference=<deposit_id>;charge_ref_resolutionusaGET /v1/payments/{id}. A fonte de verdade da cobrança paga é essa consulta, nunca o callback. - Idempotência:
X-Idempotency-Keydeterminística derivada dodeposit_id(32 hex de SHA-256 de um texto fixo mais o id). Repetir a criação do mesmo depósito devolve o mesmo pagamento. - Pagador: o e-mail do pagador é obrigatório; sem ele o adaptador recusa com
upstream_rejectedsem chamar o parceiro. - Validade: a expiração pedida é presa à faixa aceita pelo parceiro (31 minutos a 30 dias menos 1 hora) e o instante efetivo é devolvido em
expires_at. - Sem saque:
payout_key: noneedirectionssó comcash_in.
chain¶
Provedor de recebimento em cripto (dracma_chain_rs). Cobrança por endereço único derivado de xpub (watch-only), progresso por fatos de consulta e por eventos no barramento. Nunca afirma paid: quem decide é a api. Não há saque na v1.
{
"name": "chain",
"display_name": "Cripto (on-chain)",
"version": "1.0.0",
"spp_version": "1.0",
"spp_versions_supported": ["1.0"],
"synthetic": false,
"capabilities": {
"directions": ["cash_in"],
"methods": [
{
"method": "crypto",
"directions": ["cash_in"],
"assets": [
{ "code": "BTC", "network": "bitcoin", "exponent": 8, "confirmations_required": 3, "testnet": false, "payment_uri_scheme": "bitcoin" },
{ "code": "LTC", "network": "litecoin", "exponent": 8, "confirmations_required": 6, "testnet": false, "payment_uri_scheme": "litecoin" },
{ "code": "ETH", "network": "ethereum", "exponent": 18, "confirmations_required": 12, "testnet": false, "payment_uri_scheme": "ethereum" },
{ "code": "USDT", "network": "ethereum", "exponent": 6, "confirmations_required": 12, "testnet": false, "payment_uri_scheme": "ethereum" },
{ "code": "USDC", "network": "ethereum", "exponent": 6, "confirmations_required": 12, "testnet": false, "payment_uri_scheme": "ethereum" },
{ "code": "TRX", "network": "tron", "exponent": 6, "confirmations_required": 20, "testnet": false, "payment_uri_scheme": "tron" },
{ "code": "USDT", "network": "tron", "exponent": 6, "confirmations_required": 20, "testnet": false, "payment_uri_scheme": "tron" }
]
}
],
"balance": false,
"charge_lookup": true,
"charge_ref_resolution": false,
"charge_cancel": true,
"credential_check": false,
"key_lookup": { "dict": false, "key_types": [] },
"fee_reporting": "none",
"refunds": { "payout_returns": false, "charge_refund": false },
"callbacks": false,
"event_delivery": "bus"
},
"quirks": {
"not_found_is_definitive": false,
"payout_return_hypothetical": false
},
"credential_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"properties": {}
},
"config_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"properties": {
"xpubs": {
"type": "object",
"additionalProperties": false,
"properties": {
"bitcoin": { "type": "string", "x-label": "xpub Bitcoin" },
"litecoin": { "type": "string", "x-label": "xpub Litecoin" },
"ethereum": { "type": "string", "x-label": "xpub Ethereum" },
"tron": { "type": "string", "x-label": "xpub Tron" }
},
"x-label": "Chaves públicas estendidas (watch-only)",
"x-help": "Uma por rede. A chave privada nunca existe neste provedor. Mainnet fora de prd é recusada no boot."
},
"node_urls": {
"type": "object",
"additionalProperties": false,
"properties": {
"bitcoin": { "type": "string", "format": "uri" },
"litecoin": { "type": "string", "format": "uri" },
"ethereum": { "type": "string", "format": "uri" },
"tron": { "type": "string", "format": "uri" }
},
"x-label": "URLs dos nós por rede",
"x-help": "Cada host precisa estar em egress_hosts."
}
}
},
"environments": {
"sandbox": { "base_url": "https://node-testnet.exemplo.example" },
"live": { "base_url": "https://node-mainnet.exemplo.example" }
},
"environment_policy": { "live_outside_prd": "forbidden" },
"egress_hosts": [
"node-testnet.exemplo.example:443",
"node-mainnet.exemplo.example:443"
],
"callbacks": {
"signature": null,
"source_ips": { "sandbox": [], "live": [] }
},
"idempotency": {
"charge_key": "charge_id",
"payout_key": "none",
"upstream_dedupe": false,
"retention_hours": 168
},
"rate_limits": { "requests_per_second": 20, "burst": 40, "per": "provider" },
"timeouts": { "default_ms": 15000, "max_ms": 30000 },
"links": { "support": "dev@dracma.invalid" }
}
Notas do provedor cripto (contrato SPP v1, seção 15):
codeenetworkseparados: no descriptor o ativo é{code: "USDT", network: "tron"}; nas rotas públicas da api ele é escritoUSDT.TRON. As capacidades do provedor mandam: a api montaGET /currenciese validaasset_not_supporteda partir daqui. Provedor ausente significa capacidades vazias e cripto indisponível, sem derrubar o boot da api.- Confirmações e expoentes acima seguem o catálogo piso do contrato de cobrança em cripto; o provedor pode declarar outro valor, e é o descriptor que vale.
- Credencial vazia: não há segredo de tenant. A xpub e os nós vão em
config(não são segredo de assinatura; a chave privada nunca existe aqui). - Sem callback:
callbacks: false, assinaturanull. O progresso chega porcharges/get(state: observedcomfacts:received,confirmations,confirmations_required,txids,reorged) e por eventosdracma.<env>.chain.*.v1via outbox (event_delivery: bus). Barramento fora do ar: o outbox acumula e retoma, e a api reconcilia porcharges/get. - Ambiente:
environments.sandboxé testnet;liveé mainnet, só emprd. Chave estendida de mainnet fora deprdfaz o provedor recusar subir (environment_mismatch). Fora deprdas instruções saem comtestnet: true. - Idempotência: por
charge_id; repetir devolve o mesmo endereço (derivation_indexnão é reutilizado). A retenção de 168 horas é do repositório local do provedor (upstream_dedupe: false). - Saúde:
GET /healthtrazcheckspor rede (head_height,lag_seconds,nodes_agree);degradedquando nó atrás ou divergente. - Saque fora da v1:
directionssó comcash_in;POST /payoutsresponde501.
Os hosts de nó do exemplo são fictícios
Os domínios exemplo.example do descriptor chain são ilustrativos. O ops preenche egress_hosts com os nós reais de cada ambiente. A chave estendida (xpub) revela todos os endereços da operação: trate como dado sensível mesmo estando em config, restrinja o acesso ao arquivo do usuário Linux do provedor e não a escreva em log.
Erros comuns¶
| Sintoma | Causa provável | Correção |
|---|---|---|
Registro recusa com incompatible_spp_version |
spp_versions_supported sem versão que o executor fale |
Inclua a versão comum (1.0) |
422 misconfigured com field ao criar conexão |
Campo fora do credential_schema ou config_schema (por additionalProperties: false) |
Corrija o payload ou o schema |
Cobrança funciona em dev e falha em hml com environment_forbidden |
Provedor synthetic: false sem environments.sandbox e sem a chave PSP_REAL_OUTSIDE_PRD |
Declare sandbox ou use a exceção auditada de hml |
Callback do parceiro volta 401 para tudo |
callbacks.signature com header em maiúsculas, ou segredo do webhook trocado |
Declare o cabeçalho em minúsculas e confira webhook_secret |
| Egress bloqueado em hml | Host de algum environments não está em egress_hosts |
Inclua todos os hosts e reaplique o egress com o ops |
Provedor volta a registered sozinho |
Mudança incompatível de descriptor | Reaplique egress/ingress, certifique o novo hash e faça enable |
Capacidade true e a rota responde 501 |
Descriptor mente | Corrija o descriptor ou implemente a rota; a certificação falha nos dois casos |
Erro de formato no egress_hosts |
Faltou a porta | Use sempre host:porta |
name rejeitado |
Maiúsculas, espaço ou mais de 64 caracteres | Use ^[a-z0-9_-]{1,64}$ |
Próximos passos¶
- Segurança do provedor: seguranca.md (isolamento, egress por UID, credencial por chamada, modelo de ameaça e checklist).
- Depois do descriptor, implemente as rotas do protocolo e passe a suíte de conformidade antes de pedir
enableem hml ou prd.