# HMAC-SHA512

Todas las solicitudes `POST` en `/api/external/*` (cash-in, cash-out, refund, webhooks, validacion de CPF) exigen firma HMAC-SHA512 del body para garantizar integridad y autenticidad del payload.

::: info Alcance
- **Solo `POST`**. Solicitudes `GET` y `DELETE` no necesitan el header `hmac` (el plug no esta en el pipeline de ellas).
- **Endpoints cubiertos**: `POST /pix/cash-in`, `POST /pix/cash-out`, `POST /pix/refund`, `POST /webhooks`, `POST /cpf/validate`.
- **Header obligatorio**: `hmac` en hexadecimal lowercase (la API normaliza a lowercase antes de comparar, pero siempre envie ya en lowercase para evitar ambiguedad).
:::

## Como Funciona

1. Serialice el body de la solicitud como JSON **sin indentacion** (compact) con las claves en **orden alfabetico**
2. Genere la firma HMAC-SHA512 usando su `client_secret` como clave
3. Envie la firma en el header `hmac` en formato hexadecimal lowercase
4. Envie **el mismo body** (exactamente como fue firmado) en el cuerpo de la solicitud

La API recalcula el hash sobre el body recibido (despues de la normalizacion interna) y lo compara con el valor del header `hmac` usando **comparacion en tiempo constante** (constant-time comparison), impidiendo ataques de timing que intentan descubrir la firma byte a byte.

## Normalizacion del Body (critico)

El servidor normaliza el body recibido **antes** de calcular el HMAC. Para que la firma sea valida, el cliente debe firmar el body exactamente en la forma normalizada que usa el servidor:

1. `JSON.parse` del body recibido
2. **Re-serializar con las claves en orden alfabetico** (el servidor reordena las claves antes de calcular el HMAC esperado)
3. `JSON.stringify` **sin indentacion** (equivalente a `json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False)` en Python)
4. Remover **1 espacio** que exista despues de cada `:` o `,` (convierte `", "` a `","` y `": "` a `":"`). Defensivo -- `JSON.stringify(obj)` y `json.dumps(obj, separators=(',', ':'))` nunca producen esos espacios; serializadores que emiten `", "` entre pares (ej: `json.dumps` por defecto en Python) quedan automaticamente compatibles
5. Computar `HMAC-SHA512(body_normalizado, client_secret)` en hexadecimal lowercase
6. Enviar el **mismo body normalizado** (con claves ordenadas alfabeticamente, sin espacios despues de `:` y `,`) como request body y el HMAC en el header `hmac`

::: danger Orden alfabetico de claves es OBLIGATORIO
**El servidor reordena las claves del JSON en orden alfabetico antes de calcular el HMAC esperado.** Si el cliente firma el body en otro orden, la firma **no coincide** y la solicitud se rechaza con `HTTP 401 Invalid HMAC signature`.

Ejemplo concreto:

- Cliente envia `{"external_id":"...","amount":1000}` y firma exactamente esa cadena
- Servidor internamente reordena a `{"amount":1000,"external_id":"..."}` antes de validar
- Los HMAC difieren → **solicitud rechazada**

Solucion: ordene las claves alfabeticamente antes de serializar y firmar, y envie exactamente la misma cadena ordenada en el cuerpo de la solicitud.
:::

::: warning Bibliotecas que NO ordenan por defecto
Si usa Java Jackson, C# `System.Text.Json` o PHP `json_encode`, el orden de las claves **no** es alfabetico por defecto. Debe forzar el orden.

- **Java (Jackson)**: use `mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)` o serialice desde un `TreeMap<String, Object>` (mantiene las claves ordenadas).
- **C#**: `JsonSerializerOptions` no ordena; ordene via `SortedDictionary<string, object>` antes de serializar.
- **PHP**: llame a `ksort($array)` antes de `json_encode($array)`.
- **Python**: `json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False)` - nativo, sin configuracion extra.
- **Go**: `json.Marshal` desde un `map[string]interface{}` ya garantiza orden alfabetico desde Go 1.12+.
- **JavaScript/Node**: `JSON.stringify` preserva orden de insercion; ordene las claves 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 con JSON.stringify(obj, null, 2)
`JSON.stringify(obj)` (sin indentacion) produce `{"a":1,"b":2}` - sin espacios, ya normalizado.

`JSON.stringify(obj, null, 2)` (con indentacion para humanos) produce:
```
{
  "a": 1,
  "b": 2
}
```
- con espacios despues de `:` y newlines. **Esto rompe el HMAC**. Use siempre `JSON.stringify(obj)` sin indentacion para computar y enviar el HMAC.
:::

::: danger No escape caracteres no-ASCII (acentuacion)
El servidor canoniza el body como **UTF-8 crudo** (acentos como bytes UTF-8, sin `\uXXXX`). Serializadores que escapan no-ASCII por default producen una firma que **no coincide** cuando el payload tiene acentos (ej.: `"descrição"`, `"São Paulo"`):

- **Python**: use `ensure_ascii=False` (el default `True` escapa a `\uXXXX`).
- **PHP**: use `JSON_UNESCAPED_UNICODE` junto con `JSON_UNESCAPED_SLASHES`.
- **C#**: `System.Text.Json` escapa no-ASCII (y hasta `<`, `>`, `&`, `+`) por default; configure `Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping` y valide con un payload acentuado antes de produccion.
- **JavaScript/Java (Jackson)/Go**: ya emiten UTF-8 crudo por default, nada que hacer.
:::

::: tip HMAC-SHA512 vs Webhook SHA256
La API usa **HMAC-SHA512** para autenticar solicitudes enviadas por usted. Los webhooks enviados por Monetarie usan **HMAC-SHA256** para firma (header `X-Monetarie-Signature`). Son algoritmos diferentes -- cada uno en su contexto. Vea [Webhooks](/es/webhooks) para detalles de la validacion de webhooks.

Otra diferencia importante: en el **request HMAC-SHA512 (sentido cliente → Monetarie)** el servidor reordena alfabeticamente las claves antes de comparar, por lo que el cliente debe producir el body ordenado. En el **webhook HMAC-SHA256 (sentido Monetarie → cliente)** el cliente recibe el body exactamente como fue enviado (sin reordenacion posterior) y debe validar la firma sobre ese body RAW -- no re-serialize antes de validar.
:::

## Headers Obligatorios

| Header | Valor | Notas |
|--------|-------|-------|
| `Authorization` | `ApiKey {client_id}:{client_secret}` o `Basic {base64(client_id:client_secret)}` | Misma credencial, dos formatos aceptados |
| `Content-Type` | `application/json` | `POST` sin `Content-Type: application/json` → 415 antes incluso del HMAC |
| `hmac` | Firma HMAC-SHA512 en **hexadecimal lowercase** | 128 caracteres hex. Si envia en mayusculas, el servidor normaliza a lowercase antes de comparar -- pero envie ya en lowercase por convencion |

## Ejemplos

### JavaScript (Node.js)

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

// Claves en orden alfabetico (amount < description < pix_key < pix_key_type)
const payload = {
  amount: 3000,
  description: "Pagamento",
  pix_key: "12345678901",
  pix_key_type: "cpf"
};

// JSON.stringify preserva orden de insercion, NO ordena por clave.
// Incluso si el objeto anterior esta alfabetico, forzar sort explicitamente
// garantiza que el body coincida con el hash esperado por el 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 y `hmac` en el header hmac.
```

### Python

```python
import hmac
import hashlib
import json

# sort_keys=True garantiza orden alfabetico - OBLIGATORIO
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 garantiza orden alfabetico de claves - OBLIGATORIO
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 mantiene las claves en orden alfabetico - OBLIGATORIO
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 mantiene las claves en orden alfabetico - OBLIGATORIO
// (JsonSerializerOptions NO ordena claves por defecto)
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"
```

## Validacion

La API recalcula el HMAC-SHA512 del body recibido (despues de normalizacion) y lo compara con el valor recibido en el header `hmac` usando comparacion en tiempo constante. Todos los errores del plug `HmacValidation` siguen el formato `{"worked": false, "detail": "..."}`:

### 401 -- Firma invalida o header ausente

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

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

### 400 -- Body ausente o JSON invalido

```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 sin HMAC secret configurado

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

::: tip Checklist de integracion HMAC
- Use el body **exactamente** como se envia en la solicitud (misma serializacion JSON, claves en **orden alfabetico**, sin espacios despues de `:` y `,`).
- El `client_secret` de la API Key es la clave HMAC -- el mismo valor enviado en `Authorization: ApiKey {client_id}:{client_secret}` (o dentro del `base64` cuando usa `Authorization: Basic`).
- La firma debe tener **128 caracteres hexadecimales en lowercase** (SHA-512 produce 64 bytes = 128 hex chars). La API normaliza el header a lowercase antes de comparar, pero envie ya en lowercase por convencion. Mayusculas o mixto (ej: `AbCdEf`) funcionan pero son desaconsejados.
- La comparacion se hace en tiempo constante -- no hay como descubrir la firma por timing.
- Si su cliente envia `X-Key-Case: camelCase`, firme el body **en la forma que esta transitando en la red** (camelCase). El HMAC siempre ve el body tal como usted lo envio, antes de cualquier conversion interna -- ver [Autenticacion -- Headers opcionales](/es/auth-token#headers).
:::
