Sign in
Introdução/Autenticação

Autenticação e assinatura de requisições

Assine requisições da API com HMAC-SHA256 usando o UUID do seu projeto e sua API key.

Toda requisição da API (exceto webhooks recebidos) deve carregar o UUID do seu projeto e uma assinatura. A assinatura prova que a requisição veio de você e que ninguém a alterou no caminho.

Chaves de API

A 2328.io utiliza duas chaves que compartilham o mesmo algoritmo de assinatura, mas cobrem endpoints diferentes:

ChaveUsada para
API keyPagamentos, carteiras estáticas, saldo, taxas de câmbio e verificação de webhooks de pagamento / carteira estática
Payout API keyTodos os endpoints /v1/payout/* e verificação de webhooks de saque

Ambas as chaves ficam nas configurações do projeto em 2328.io. Os exemplos abaixo dizem "API key" de forma genérica — substitua pela chave correta para o endpoint que você está chamando.

Nunca misture as duas chaves: assinar uma requisição de saque com a API key comum (ou uma requisição de pagamento com a Payout key) retorna um erro de assinatura.

Cabeçalhos obrigatórios

CabeçalhoTipoObrigatórioDescrição
Content-TypestringsimSempre application/json
projectstringsimUUID do seu projeto
signstringsimAssinatura HMAC-SHA256 da requisição, calculada com sua API key
User-AgentstringsimIdentifica sua aplicação (ex.: MyShop/1.4 (+https://myshop.example)). Requisições sem User-Agent podem ser bloqueadas.

Como funciona a assinatura

Pense na assinatura como uma impressão digital do corpo da requisição. Ela é construída assim:

  1. Serialize o corpo em JSON (compacto — sem espaços extras).
  2. Codifique esse JSON em base64. Esse passo normaliza a entrada entre linguagens — uma vez convertido para ASCII puro, todas as linguagens produzem os mesmos bytes para o HMAC.
  3. Calcule HMAC-SHA256 da string base64 usando sua API key e converta o resultado para hexadecimal em minúsculas.

Para requisições GET e outros tipos sem corpo, assine uma string vazia em vez do JSON.

A assinatura de string vazia é constante para uma dada API key. Você pode armazená-la em cache se fizer muitas chamadas GET.

Implementações

PHP
<?php
function apiSign(array $data, string $apiKey): string {
    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $base64 = base64_encode($json);
    return hash_hmac('sha256', $base64, $apiKey);
}

Requisições sem corpo (GET)

Para requisições com corpo vazio (por exemplo, GET /v1/payout/status/{uuid}), assine uma string vazia:

Shell
SIGN=$(printf '' | openssl dgst -sha256 -hmac "$API_KEY" -hex | awk '{print $NF}')

Exemplo completo de requisição

Shell
curl -X POST https://api.2328.io/api/v1/payment \
  -H "Content-Type: application/json" \
  -H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
  -H "project: YOUR_PROJECT_UUID" \
  -H "sign: YOUR_HMAC_SIGNATURE" \
  -d '{"amount":"100.00","currency":"USD","order_id":"ORDER-123"}'

Nunca exponha sua API key em código do lado do cliente. Assine as requisições no seu backend. Uma API key vazada dá a qualquer pessoa acesso total à sua conta de comerciante.

Verificando assinaturas de webhook

Quando a 2328.io envia um webhook para você, o mesmo algoritmo é executado no sentido inverso:

  1. Extraia o campo sign do payload.
  2. Codifique os campos restantes em JSON (compacto, sem espaços).
  3. Codifique essa string em base64.
  4. Calcule HMAC-SHA256 com a chave apropriada.
  5. Compare com o sign recebido usando uma comparação em tempo constante (hash_equals, crypto.timingSafeEqual, hmac.compare_digest, subtle.ConstantTimeCompare, OpenSSL.fixed_length_secure_compare).

A chave de assinatura depende da origem do webhook:

WebhookChave para verificar
Webhooks de pagamento / carteira estática (/v1/payment, /v1/static-wallet)API key
Webhooks de saque (/v1/payout)Payout API key

Erros comuns de verificação. Seu codificador JSON deve produzir exatamente os mesmos bytes que o remetente produziu — caso contrário o Base64 difere e a assinatura não vai bater.

  • Go: use json.NewEncoder com SetEscapeHTML(false). O json.Marshal padrão escapa <, >, & para < e quebra a assinatura.
  • Python: passe ensure_ascii=False para json.dumps. Sem isso, caracteres não ASCII (cirílico, chinês, …) são escapados para \uXXXX.
  • JSON compacto: sem espaços em branco entre campos (separators=(",", ":") em Python).
  • Ordem dos campos (Go): um map[string]any puro randomiza as chaves ao recodificar. Use json.RawMessage, uma struct ordenada ou remova sign dos bytes originais.

Se a verificação continuar falhando, execute apiSign no payload você mesmo — ele deve produzir a mesma string hexadecimal que o sign recebido.

Uma assinatura válida não previne replays. Ela apenas prova que o webhook veio do 2328.io — não impede que um atacante reposte um webhook capturado mais tarde. Sempre verifique idempotência por uuid (ou txid para carteiras estáticas) antes de creditar fundos. Rejeite com HTTP 401 se a assinatura estiver ausente ou incorreta.

Exemplos de código completos estão em Webhook Notifications. Tratamento de retentativas e regras de idempotência estão em Boas práticas.