Autenticação
A InfraIO Pay tem duas superfícies de API com modelos de autenticação diferentes. Escolha a que combina com quem está chamando:
| Superfície | Prefixo do path | Audiência | Auth |
|---|---|---|---|
| B2B do lojista | /b2b/v1/* | Seu servidor | Assinatura de requisição HMAC-SHA256 |
| Dashboard | Por serviço: /auth/*, /payment/*, /merchant/*, /event/*, /user/*, … | Sessões de navegador para o dashboard do lojista | Bearer JWT |
Esta página cobre a superfície B2B — a que você chama do seu servidor com um par de chaves de API. Se você está embutindo o dashboard InfraIO ou construindo ferramentas internas, use a superfície do dashboard (docs separadas, ainda não públicas).
O gateway roteia cada superfície por um prefixo inicial que
remove antes de encaminhar: /b2b/v1/checkout-sessions/quick
chega ao payment-service como /v1/checkout-sessions/quick, e o
/payment/v1/orders do dashboard chega como /v1/orders. Então se
você vê paths /v1/* puros em outros lugares, esse é o path
backend-interno depois do prefixo público ter sido removido —
o seu cliente sempre envia a forma com prefixo. (Uma consequência
para a assinatura: a string canônica B2B assina o path com o
prefixo /b2b ainda anexado — veja abaixo.)
Endpoints
| Ambiente | Base URL |
|---|---|
| Teste | https://api-dev.infraio.xyz |
| Live | https://api.infraio.xyz |
Mesmo padrão de URL — o ambiente é controlado pelo prefixo da
chave (pk_test_… vs pk_live_…), não pela URL.
Par de chaves
Você pega dois valores no dashboard do lojista (Developers → API keys → + Add key):
- Chave publicável (
pk_test_…oupk_live_…) — identifica a sua conta. Enviada comoX-Client-ID. Seguro embutir no seu bundle do navegador (o SDK já faz isso). - Chave secreta (
sk_test_…ousk_live_…) — a chave de assinatura HMAC. Só servidor. Trate como uma senha de banco de dados.
Se uma chave secreta cair num bundle de navegador, repo git, linha de log ou chat compartilhado — revogue imediatamente no dashboard. Não há janela de sobreposição; a revogação é instantânea. Crie uma chave nova e redeploye.
Assinando uma requisição
Toda chamada para /b2b/v1/* leva três headers:
X-Client-ID: pk_live_70d0becbd7a53047b0dfe88a960d10ad
X-Timestamp: 1715990400
X-Signature: 9a8b7c6d… (hex HMAC-SHA256)A assinatura é calculada sobre uma string canônica:
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODYMETHOD— verbo HTTP em maiúsculas (POST,GET, …).PATH— path da requisição incluindo o prefixo/b2b, sem o host e sem a query string (por exemplo,/b2b/v1/checkout-sessions/quick). O gateway verifica a assinatura sobre o path bruto de entrada antes de remover/b2b, então o prefixo precisa estar presente. Parâmetros de query não são assinados — para umGET …?cursor=…&limit=20, assine só o path, não a parte?….TIMESTAMP— unix segundos, como string decimal (por exemplo,"1715990400"), batendo exatamente com oX-Timestamp.BODY— bytes brutos do body da requisição. String vazia paraGET/DELETE.
Assine com HMAC-SHA256 chaveado pela chave secreta, saída hex:
Node / TS
import { createHmac } from "node:crypto";
function sign({ method, path, body, secret }: {
method: string; path: string; body: string; secret: string;
}) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const input = [method.toUpperCase(), path, timestamp, body].join("\n");
const signature = createHmac("sha256", secret).update(input).digest("hex");
return { timestamp, signature };
}Por que HMAC, e não Bearer?
Uma API de token Bearer puro envia o seu único segredo pela rede em toda requisição. Quem capturar um log de proxy com terminação TLS ganha as chaves da sua conta. Assinar com HMAC significa que o segredo nunca viaja — só a assinatura derivada dele, que é de uso único (vinculada àquela requisição exata + aquele minuto exato).
O trade-off: você tem que computar a assinatura para toda chamada. Um SDK de servidor esconderia isso; até a gente publicar um, o helper acima são ~15 linhas por linguagem.
Tolerância do timestamp
A tolerância obrigatória é de ±5 minutos (300 segundos),
aplicada pelo merchant-service quando verifica a assinatura. O
próprio gateway é um pouco mais frouxo (310s) como defesa em
profundidade, mas uma requisição que passa pelo gateway e falha no
check interno ainda termina em 401 invalid_signature — considere
300s como o contrato. Duas implicações:
- Sincronize o relógio do seu servidor via NTP. Um cron de longa duração com relógio desviado vai falhar intermitentemente.
- Não pré-compute e enfileire assinaturas. Se uma requisição fica numa fila de retry por mais de 5 min, a assinatura expira.
Escopos de chave
Chaves secretas carregam um ou mais destes bundles de escopo:
| Escopo | Uso pretendido |
|---|---|
read | Listar/ler pedidos, sessões, reembolsos |
write_order | Criar sessões de checkout, pedidos |
write_refund | Emitir reembolsos, criar tokens de pedido de reembolso |
webhook_manage | Criar/atualizar/deletar endpoints de webhook |
O dashboard emite uma chave “full access” por padrão (os quatro escopos). Você pode criar uma chave com escopo restrito em Developers → API keys → + Add key e marcar só os escopos que a integração precisa.
A aplicação de escopo é consultiva no momento, não bloqueante.
Os escopos são registrados na chave e exibidos de volta para você
no dashboard, mas o middleware do gateway ainda não rejeita
chamadas fora do escopo — qualquer chave sk_… válida se comporta
como full-access hoje. Aplicação de escopo por endpoint está na
próxima release. Não confie em escopos como uma fronteira de
segurança ainda; trate como rótulos e rotacione / revogue chaves
para restringir acesso enquanto isso.
Onde a assinatura é verificada
A validação HMAC acontece uma vez, no gateway. O gateway:
- Lê
X-Client-ID,X-Timestamp,X-Signature. - Busca o lojista + segredo pelo
pk_…, roda o check de janela de timestamp, recomputa a assinatura, compara em tempo constante. - Em sucesso, remove os headers de auth, carimba a requisição
com headers internos (
X-B2B-Auth: 1,X-Merchant-ID,X-Merchant-Domain) e encaminha para o serviço downstream (payment-service, merchant-service, etc.). Ambiente + escopos resolvidos NÃO são injetados hoje — código downstream que precisa do ambiente o deriva do body da requisição / config por lojista, não dos headers. - Em falha, retorna 401
INVALID_SIGNATUREsem nunca tocar no backend.
Serviços downstream não re-rodam o HMAC — eles confiam nos
headers injetados pelo gateway e agem no lojista que o gateway
resolveu. Eles não aplicam escopo por endpoint também: como
notado acima, o escopo da chave não é injetado, então qualquer
sk_… autenticado chega em qualquer endpoint para o seu lojista
(aplicação de escopo é consultiva hoje — veja o callout em
Escopos de chave). Isso importa de dois jeitos:
- Se você opera o seu próprio reverse proxy na frente da InfraIO
Pay, não remova
X-B2B-Auth/X-Merchant-ID(e também não forje — o gateway rejeita requisições de entrada que carreguem esses na borda pública). - Os paths de rede pública (
/b2b/v1/*) são a única superfície que roda o passo HMAC. gRPC interno entre os nossos serviços usa mTLS — um modelo de confiança diferente que não aceitaX-Client-ID.
Próximos passos
- Erros — formato da resposta em 4xx/5xx.
- Segurança → Chaves de API — rotação, revogação, o que fazer se um segredo vaza.
- Webhooks → Verificação de assinatura
— usa um esquema HMAC diferente (header
X-Signature: sha256=…, assinaX-Timestamp + "." + raw_body, mais umX-Signature-Prevopcional durante a janela de tolerância de 24 horas da rotação). Não misture os esquemas — eles compartilham o algoritmo de hash mas os bytes assinados e a família de segredos (whsec_…vssk_…) são diferentes.