# HMAC-SHA512

Todas as requisições `POST` em `/api/external/*` (cash-in, cash-out, refund, webhooks, validação de CPF) exigem assinatura HMAC-SHA512 do body para garantir integridade e autenticidade do payload.

::: info Escopo
- **Só `POST`**. Requisições `GET` e `DELETE` não precisam do header `hmac` (o plug não está no pipeline delas).
- **Endpoints cobertos**: `POST /pix/cash-in`, `POST /pix/cash-out`, `POST /pix/refund`, `POST /webhooks`, `POST /cpf/validate`.
- **Header obrigatório**: `hmac` em hexadecimal lowercase (a API normaliza para lowercase antes de comparar, mas sempre envie já em lowercase para evitar ambiguidade).
:::

## Como Funciona

1. Serialize o body da requisição como JSON **sem indentação** (compact) com as chaves em **ordem alfabética**
2. Gere a assinatura HMAC-SHA512 usando seu `client_secret` como chave
3. Envie a assinatura no header `hmac` em formato hexadecimal lowercase
4. Envie **o mesmo body** (exatamente como foi assinado) no corpo da requisição

A API recalcula o hash sobre o body recebido (após a normalização interna) e compara com o valor do header `hmac` usando **comparação em tempo constante** (constant-time comparison), impedindo ataques de timing que tentam descobrir a assinatura byte a byte.

## Normalização do Body (crítico)

O servidor normaliza o body recebido **antes** de calcular o HMAC. Para a assinatura ser válida, o cliente deve assinar o body exatamente na forma normalizada que o servidor usa:

1. `JSON.parse` do body recebido
2. **Re-serializar com as chaves em ordem alfabética** (o servidor reordena as chaves antes de calcular o HMAC esperado)
3. `JSON.stringify` **sem indentação** (equivalente a `json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False)` em Python)
4. Remover **1 espaço** que exista depois de cada `:` ou `,` (converte `", "` para `","` e `": "` para `":"`). Defensivo -- `JSON.stringify(obj)` e `json.dumps(obj, separators=(',', ':'))` nunca produzem esses espaços; serializadores que emitem `", "` entre pares (ex: `json.dumps` padrão em Python) ficam automaticamente compatíveis
5. Computar `HMAC-SHA512(body_normalizado, client_secret)` em hexadecimal lowercase
6. Enviar o **mesmo body normalizado** (com chaves ordenadas alfabeticamente, sem espaços após `:` e `,`) como request body e o HMAC no header `hmac`

::: danger Ordenação alfabética de chaves é OBRIGATÓRIA
**O servidor reordena as chaves do JSON em ordem alfabética antes de calcular o HMAC esperado.** Se o cliente assina o body em outra ordem, a assinatura **não bate** e a requisição é rejeitada com `HTTP 401 Invalid HMAC signature`.

Exemplo concreto:

- Cliente envia `{"external_id":"...","amount":1000}` e assina exatamente essa string
- Servidor internamente reordena para `{"amount":1000,"external_id":"..."}` antes de validar
- HMACs diferem → **requisição rejeitada**

Solução: ordene as chaves alfabeticamente antes de serializar e assinar, e envie exatamente essa mesma string ordenada no corpo da requisição.
:::

::: warning Bibliotecas que NÃO ordenam por padrão
Se você usa Java Jackson, C# `System.Text.Json` ou PHP `json_encode`, a ordem das chaves **não** é alfabética por padrão. Você precisa forçar a ordenação.

- **Java (Jackson)**: use `mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)` ou serialize a partir de um `TreeMap<String, Object>` (que mantém chaves ordenadas).
- **C#**: `JsonSerializerOptions` não ordena; ordene via `SortedDictionary<string, object>` antes de serializar.
- **PHP**: chame `ksort($array)` antes de `json_encode($array)`.
- **Python**: `json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False)` - nativo, sem configuração extra.
- **Go**: `json.Marshal` de um `map[string]interface{}` já garante ordem alfabética desde Go 1.12+.
- **JavaScript/Node**: `JSON.stringify` preserva ordem de inserção; ordene as chaves manualmente antes de serializar:
  ```javascript
  const sorted = Object.keys(obj).sort().reduce((acc, key) => { acc[key] = obj[key]; return acc; }, {});
  const body = JSON.stringify(sorted);
  ```
:::

::: warning Cuidado com JSON.stringify(obj, null, 2)
`JSON.stringify(obj)` (sem indentação) produz `{"a":1,"b":2}` - sem espaços, já normalizado.

`JSON.stringify(obj, null, 2)` (com indentação para humanos) produz:
```
{
  "a": 1,
  "b": 2
}
```
- com espaços depois de `:` e newlines. **Isso quebra o HMAC**. Use sempre `JSON.stringify(obj)` sem indentação para computar e enviar o HMAC.
:::

::: danger Não escape caracteres não-ASCII (acentuação)
O servidor canoniza o body em **UTF-8 cru** (acentos como bytes UTF-8, sem `\uXXXX`). Serializadores que escapam não-ASCII por default produzem assinatura que **não bate** quando o payload tem acentuação (ex.: `"descrição"`, `"São Paulo"`):

- **Python**: use `ensure_ascii=False` (o default `True` escapa para `\uXXXX`).
- **PHP**: use `JSON_UNESCAPED_UNICODE` junto com `JSON_UNESCAPED_SLASHES`.
- **C#**: `System.Text.Json` escapa não-ASCII (e até `<`, `>`, `&`, `+`) por default; configure `Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping` e valide com um payload acentuado antes de ir a produção.
- **JavaScript/Java (Jackson)/Go**: já emitem UTF-8 cru por default, nada a fazer.
:::

::: tip HMAC-SHA512 vs Webhook SHA256
A API usa **HMAC-SHA512** para autenticar requisições enviadas por você. Os webhooks enviados pela Monetarie usam **HMAC-SHA256** para assinatura (header `X-Monetarie-Signature`). São algoritmos diferentes -- cada um no seu contexto. Veja [Webhooks](/webhooks) para detalhes da validação de webhooks.

Outra diferença importante: no **request HMAC-SHA512 (sentido cliente → Monetarie)** o servidor reordena alfabeticamente as chaves antes de comparar, então o cliente deve produzir o body ordenado. No **webhook HMAC-SHA256 (sentido Monetarie → cliente)** o cliente recebe o body exatamente como foi enviado (sem reordenação posterior) e deve validar a assinatura sobre esse body RAW -- não re-serialize antes de validar.
:::

## Headers Obrigatórios

| Header | Valor | Notas |
|--------|-------|-------|
| `Authorization` | `ApiKey {client_id}:{client_secret}` ou `Basic {base64(client_id:client_secret)}` | Mesma credencial, dois formatos aceitos |
| `Content-Type` | `application/json` | `POST` sem `Content-Type: application/json` → 415 antes mesmo do HMAC |
| `hmac` | Assinatura HMAC-SHA512 em **hexadecimal lowercase** | 128 caracteres hex. Se enviar em maiúsculas, o servidor normaliza para lowercase antes de comparar -- mas envie já em lowercase por convenção |

## Exemplos

### JavaScript (Node.js)

```javascript
const crypto = require('crypto');

// Keys em ordem alfabética (amount < description < pix_key < pix_key_type)
const payload = {
  amount: 3000,
  description: "Pagamento",
  pix_key: "12345678901",
  pix_key_type: "cpf"
};

// JSON.stringify preserva ordem de inserção, NÃO ordena por chave.
// Mesmo que o objeto acima esteja alfabético, forçar sort explicitamente
// garante que o body casa com o hash esperado pelo servidor.
const sortedKeys = Object.keys(payload).sort();
const body = JSON.stringify(
  sortedKeys.reduce((acc, key) => ({ ...acc, [key]: payload[key] }), {})
);
// body = '{"amount":3000,"description":"Pagamento","pix_key":"12345678901","pix_key_type":"cpf"}'

const hmac = crypto
  .createHmac('sha512', 'sk_seu-client-secret')
  .update(body)
  .digest('hex');

// Enviar `body` como request body e `hmac` no header hmac.
```

### Python

```python
import hmac
import hashlib
import json

# sort_keys=True garante ordem alfabética - OBRIGATÓRIO
body = json.dumps(
    {
        "amount": 3000,
        "pix_key": "12345678901",
        "pix_key_type": "cpf",
        "description": "Pagamento"
    },
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False
)
# body = '{"amount":3000,"description":"Pagamento","pix_key":"12345678901","pix_key_type":"cpf"}'

signature = hmac.new(
    b"sk_seu-client-secret",
    body.encode("utf-8"),
    hashlib.sha512
).hexdigest()
```

### PHP

```php
$data = [
    'amount' => 3000,
    'pix_key' => '12345678901',
    'pix_key_type' => 'cpf',
    'description' => 'Pagamento'
];

// ksort garante ordem alfabética de chaves - OBRIGATÓRIO
ksort($data);
$body = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

$hmac = hash_hmac('sha512', $body, 'sk_seu-client-secret');
```

### Java

```java
import java.util.TreeMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

// TreeMap mantém as chaves em ordem alfabética - OBRIGATÓRIO
TreeMap<String, Object> bodyMap = new TreeMap<>();
bodyMap.put("amount", 3000);
bodyMap.put("pix_key", "12345678901");
bodyMap.put("pix_key_type", "cpf");
bodyMap.put("description", "Pagamento");

ObjectMapper mapper = new ObjectMapper();
String body = mapper.writeValueAsString(bodyMap);
// body = {"amount":3000,"description":"Pagamento","pix_key":"12345678901","pix_key_type":"cpf"}

String secret = "sk_seu-client-secret";
Mac mac = Mac.getInstance("HmacSHA512");
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA512");
mac.init(keySpec);
byte[] hash = mac.doFinal(body.getBytes(StandardCharsets.UTF_8));

StringBuilder sb = new StringBuilder();
for (byte b : hash) {
    sb.append(String.format("%02x", b));
}
String hmac = sb.toString();
```

### C#

```csharp
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

// SortedDictionary mantém as chaves em ordem alfabética - OBRIGATÓRIO
// (JsonSerializerOptions NÃO ordena chaves por padrão)
var payload = new SortedDictionary<string, object>
{
    ["amount"] = 3000,
    ["description"] = "Pagamento",
    ["pix_key"] = "12345678901",
    ["pix_key_type"] = "cpf"
};

var body = JsonSerializer.Serialize(payload);
// body = {"amount":3000,"description":"Pagamento","pix_key":"12345678901","pix_key_type":"cpf"}

var secret = Encoding.UTF8.GetBytes("sk_seu-client-secret");

using var hmacSha512 = new HMACSHA512(secret);
var hash = hmacSha512.ComputeHash(Encoding.UTF8.GetBytes(body));
var hmac = BitConverter.ToString(hash).Replace("-", "").ToLower();
```

### Bash (curl)

```bash
BODY='{"amount":3000,"description":"Pagamento","pix_key":"12345678901","pix_key_type":"cpf"}'
SECRET="sk_seu-client-secret"

HMAC=$(echo -n "$BODY" | openssl dgst -sha512 -hmac "$SECRET" | awk '{print $2}')

curl -X POST https://api.monetarie.com/api/external/pix/cash-out \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "hmac: $HMAC" \
  -d "$BODY"
```

## Validação

A API recalcula o HMAC-SHA512 do body recebido (após normalização) e compara com o valor recebido no header `hmac` usando comparação em tempo constante. Todos os erros do plug `HmacValidation` seguem o formato `{"worked": false, "detail": "..."}`:

### 401 -- Assinatura inválida ou header ausente

```json
{
  "worked": false,
  "detail": "Invalid HMAC signature"
}
```

```json
{
  "worked": false,
  "detail": "Missing HMAC header"
}
```

### 400 -- Body ausente ou JSON inválido

```json
{
  "worked": false,
  "detail": "Request body is required for HMAC validation"
}
```

```json
{
  "worked": false,
  "detail": "Request body must be valid JSON for HMAC validation"
}
```

### 403 -- API Key sem HMAC secret configurado

```json
{
  "worked": false,
  "detail": "HMAC secret not configured for this API key"
}
```

::: tip Checklist de integração HMAC
- Use o body **exatamente** como enviado na requisição (mesma serialização JSON, chaves em **ordem alfabética**, sem espaços após `:` e `,`).
- O `client_secret` da API Key é a chave HMAC -- o mesmo valor enviado em `Authorization: ApiKey {client_id}:{client_secret}` (ou dentro do `base64` quando usando `Authorization: Basic`).
- A assinatura deve ter **128 caracteres hexadecimais em lowercase** (SHA-512 produz 64 bytes = 128 hex chars). A API normaliza o header para lowercase antes de comparar, mas envie já em lowercase por convenção. Maiúsculas ou misto (ex: `AbCdEf`) funcionam mas são discouraged.
- A comparação é feita em tempo constante -- não há como descobrir a assinatura por timing.
- Se o seu cliente envia `X-Key-Case: camelCase`, assine o body **na forma que está trafegando na rede** (camelCase). O HMAC sempre vê o body tal como você enviou, antes de qualquer conversão interna -- ver [Autenticação -- Headers opcionais](/auth-token#headers).
:::
