# 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:

| Chave | Usada para |
|-------|-----------|
| **API key** | Pagamentos, carteiras estáticas, saldo, taxas de câmbio e verificação de webhooks de pagamento / carteira estática |
| **Payout API key** | Todos os endpoints `/v1/payout/*` e verificação de webhooks de saque |

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

> **INFO:** **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çalho | Tipo | Obrigatório | Descrição |
|-----------|------|-------------|-----------|
| `Content-Type` | string | sim | Sempre `application/json` |
| `project` | string | sim | UUID do seu projeto |
| `sign` | string | sim | Assinatura HMAC-SHA256 da requisição, calculada com sua API key |
| `User-Agent` | string | sim | Identifica 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.

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

## Implementações

<CodeSnippet name="apiSign" langs="php,js,ts,python,go" />

### Requisições sem corpo (GET)

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

<CodeSnippet name="apiSignBodyless" langs="curl,php,js,ts,python,go" />

## Exemplo completo de requisição

<CodeSnippet name="fullRequestExample" langs="curl,php,js,ts,python,go" />

> **DANGER:** **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:

| Webhook | Chave para verificar |
|---------|----------------------|
| Webhooks de pagamento / carteira estática (`/v1/payment`, `/v1/static-wallet`) | **API key** |
| Webhooks de saque (`/v1/payout`) | **Payout API key** |

> **WARNING:** **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.

> **INFO:** **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](/docs/webhooks#verifying-the-signature)**. Tratamento de retentativas e regras de idempotência estão em [Boas práticas](/docs/webhooks#best-practices).