Pular para o conteúdo principal

Autenticação

A Banking API usa autenticação EdDSA Proof-of-Possession (PoP). Este método criptográfico garante que as requisições sejam assinadas pelo proprietário legítimo da chave privada da conta de serviço e previne ataques de replay.

Visão Geral

Diferente da autenticação tradicional por API key ou OAuth, a autenticação PoP requer que você assine criptograficamente cada requisição. Isso fornece:
  • Não-repúdio - Requisições podem ser comprovadas como originárias da sua conta de serviço
  • Proteção contra replay - Timestamps previnem reutilização de requisições capturadas

Pré-requisitos

Antes de fazer requisições à API, você precisa:
  1. Conta de Serviço - Criada no seu painel Conta Digital
  2. Par de Chaves Ed25519 - Gere um par de chaves pública/privada
  3. Chave Pública Registrada - Faça upload da sua chave pública na conta de serviço

Headers Obrigatórios

Toda requisição autenticada deve incluir estes 4 headers:
HeaderDescriçãoExemplo
x-access-idUUID da sua conta de serviço550e8400-e29b-41d4-a716-446655440000
X-PoP-SignatureAssinatura EdDSA (base64)MEUCIQDx...
X-PoP-ChallengeTimestamp Unix em milissegundos1705423200000
X-PoP-FormatIdentificador do tipo de autenticaçãoservice-account

Processo de Assinatura

A assinatura é criada assinando um formato de mensagem específico com sua chave privada Ed25519.

Passo 1: Construir a Mensagem

Concatene os seguintes valores separados por dois-pontos (:):
{uri}:{method}:{body}:{timestamp}
ComponenteDescrição
uriCaminho completo da requisição incluindo query string (ex: /v1/account?include=balance)
methodMétodo HTTP em maiúsculas (ex: GET, POST)
bodyCorpo da requisição como string, ou string vazia para requisições GET
timestampMesmo valor do header X-PoP-Challenge

Passo 2: Assinar a Mensagem

Assine a mensagem usando sua chave privada Ed25519 e codifique a assinatura em base64.

Passo 3: Definir os Headers

Inclua a assinatura e todos os headers obrigatórios na sua requisição.

Exemplos de Código

Node.js

import { sign } from '@noble/ed25519';
import { Buffer } from 'buffer';

async function signRequest({ uri, method, body, privateKey, accessId }) {
  const timestamp = Date.now().toString();
  const bodyStr = body ? JSON.stringify(body) : '';

  // Construir mensagem para assinar
  const message = `${uri}:${method}:${bodyStr}:${timestamp}`;
  const messageBytes = new TextEncoder().encode(message);

  // Assinar com Ed25519
  const signature = await sign(messageBytes, privateKey);
  const signatureBase64 = Buffer.from(signature).toString('base64');

  return {
    'x-access-id': accessId,
    'X-PoP-Signature': signatureBase64,
    'X-PoP-Challenge': timestamp,
    'X-PoP-Format': 'service-account',
    'Content-Type': 'application/json',
  };
}

// Exemplo de uso
const headers = await signRequest({
  uri: '/v1/account',
  method: 'GET',
  body: null,
  privateKey: process.env.ED25519_PRIVATE_KEY,
  accessId: process.env.SERVICE_ACCOUNT_ID,
});

const response = await fetch('https://conta-public-api.kiwify.com/v1/account', {
  method: 'GET',
  headers,
});

Python

import time
import base64
import json
import requests
from nacl.signing import SigningKey

def sign_request(uri: str, method: str, body: dict | None, private_key_hex: str, access_id: str) -> dict:
    timestamp = str(int(time.time() * 1000))
    body_str = json.dumps(body) if body else ''

    # Construir mensagem para assinar
    message = f"{uri}:{method}:{body_str}:{timestamp}"
    message_bytes = message.encode('utf-8')

    # Assinar com Ed25519
    signing_key = SigningKey(bytes.fromhex(private_key_hex))
    signed = signing_key.sign(message_bytes)
    signature_base64 = base64.b64encode(signed.signature).decode('utf-8')

    return {
        'x-access-id': access_id,
        'X-PoP-Signature': signature_base64,
        'X-PoP-Challenge': timestamp,
        'X-PoP-Format': 'service-account',
        'Content-Type': 'application/json',
    }

# Exemplo de uso
import os

headers = sign_request(
    uri='/v1/account',
    method='GET',
    body=None,
    private_key_hex=os.environ['ED25519_PRIVATE_KEY'],
    access_id=os.environ['SERVICE_ACCOUNT_ID'],
)

response = requests.get(
    'https://conta-public-api.kiwify.com/v1/account',
    headers=headers,
)

cURL (com assinatura pré-computada)

# Nota: Você precisará computar a assinatura externamente
# Este exemplo mostra o formato dos headers

TIMESTAMP=$(date +%s000)
ACCESS_ID="seu-uuid-da-conta-de-servico"
SIGNATURE="sua-assinatura-base64-computada"

curl -X GET "https://conta-public-api.kiwify.com/v1/account" \
  -H "x-access-id: ${ACCESS_ID}" \
  -H "X-PoP-Signature: ${SIGNATURE}" \
  -H "X-PoP-Challenge: ${TIMESTAMP}" \
  -H "X-PoP-Format: service-account"

Validação de Timestamp

O timestamp X-PoP-Challenge deve estar dentro de 5 minutos do horário atual do servidor. Isso previne ataques de replay enquanto permite uma margem razoável para diferença de relógios.
Certifique-se de que o relógio do seu sistema está sincronizado usando NTP. Requisições com timestamps fora da janela de 5 minutos serão rejeitadas com erro 401 Unauthorized.

Solução de Problemas

Erros Comuns

Causa: A assinatura não corresponde ao valor esperado.Soluções:
  • Verifique se está assinando o formato exato da mensagem: {uri}:{method}:{body}:{timestamp}
  • Certifique-se de que a URI inclui o caminho completo e a query string
  • Verifique se a string do body corresponde exatamente ao que está enviando
  • Confirme que sua chave privada corresponde à chave pública registrada na conta de serviço
Causa: O timestamp X-PoP-Challenge está mais de 5 minutos diferente do horário do servidor.Soluções:
  • Sincronize o relógio do seu sistema usando NTP
  • Gere o timestamp imediatamente antes de fazer a requisição
  • Certifique-se de usar milissegundos, não segundos
Causa: O x-access-id não corresponde a nenhuma conta de serviço ativa.Soluções:
  • Verifique se o UUID da conta de serviço está correto
  • Certifique-se de que a conta de serviço está ativa e não desabilitada
  • Verifique se há erros de digitação ou espaços extras

Dicas de Depuração

  1. Registre a mensagem antes de assinar - Imprima a string exata sendo assinada para verificar o formato
  2. Teste com uma assinatura conhecida - Use vetores de teste para verificar sua implementação de assinatura
  3. Verifique os headers de resposta - Respostas de erro podem incluir informações adicionais de depuração
  4. Verifique o formato da chave - Chaves privadas Ed25519 devem ter 32 bytes (64 caracteres hexadecimais)

Melhores Práticas de Segurança

Proteja Sua Chave Privada

Nunca exponha sua chave privada em código client-side, logs ou controle de versão. Use variáveis de ambiente ou gerenciamento seguro de secrets.

Rotacione Chaves Regularmente

Gere novos pares de chaves periodicamente e atualize sua conta de serviço. Revogue chaves antigas após a rotação.

Monitore o Uso da API

Revise regularmente seus logs de acesso à API para padrões inesperados ou tentativas não autorizadas.