# HMAC-SHA512

All `POST` requests on `/api/external/*` (cash-in, cash-out, refund, webhooks, CPF validation) require HMAC-SHA512 signature of the body to guarantee integrity and authenticity of the payload.

::: info Scope
- **Only `POST`**. `GET` and `DELETE` requests do not need the `hmac` header (the plug is not in their pipeline).
- **Covered endpoints**: `POST /pix/cash-in`, `POST /pix/cash-out`, `POST /pix/refund`, `POST /webhooks`, `POST /cpf/validate`.
- **Required header**: `hmac` in lowercase hexadecimal (the API normalizes to lowercase before comparing, but always send in lowercase to avoid ambiguity).
:::

## How It Works

1. Serialize the request body as JSON **without indentation** (compact) with keys in **alphabetical order**
2. Generate the HMAC-SHA512 signature using your `client_secret` as the key
3. Send the signature in the `hmac` header in lowercase hexadecimal format
4. Send **the same body** (exactly as signed) in the request body

The API recomputes the hash over the received body (after internal normalization) and compares it against the value of the `hmac` header using **constant-time comparison**, preventing timing attacks that try to discover the signature byte by byte.

## Body Normalization (critical)

The server normalizes the received body **before** computing the HMAC. For the signature to be valid, the client must sign the body exactly in the normalized form the server uses:

1. `JSON.parse` the received body
2. **Re-serialize with keys in alphabetical order** (the server reorders keys before computing the expected HMAC)
3. `JSON.stringify` **without indentation** (equivalent to `json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False)` in Python)
4. Remove **1 space** that exists after each `:` or `,` (converts `", "` to `","` and `": "` to `":"`). Defensive -- `JSON.stringify(obj)` and `json.dumps(obj, separators=(',', ':'))` never produce those spaces; serializers that emit `", "` between pairs (e.g., default `json.dumps` in Python) become automatically compatible
5. Compute `HMAC-SHA512(normalized_body, client_secret)` in lowercase hexadecimal
6. Send the **same normalized body** (with keys alphabetically ordered, without spaces after `:` and `,`) as the request body and the HMAC in the `hmac` header

::: danger Alphabetical ordering of keys is MANDATORY
**The server reorders JSON keys alphabetically before computing the expected HMAC.** If the client signs the body in another order, the signature **will not match** and the request is rejected with `HTTP 401 Invalid HMAC signature`.

Concrete example:

- Client sends `{"external_id":"...","amount":1000}` and signs exactly this string
- Server internally reorders to `{"amount":1000,"external_id":"..."}` before validating
- HMACs differ → **request rejected**

Solution: order the keys alphabetically before serializing and signing, and send exactly the same ordered string in the request body.
:::

::: warning Libraries that do NOT order by default
If you use Java Jackson, C# `System.Text.Json`, or PHP `json_encode`, the order of keys is **not** alphabetical by default. You need to force ordering.

- **Java (Jackson)**: use `mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)` or serialize from a `TreeMap<String, Object>` (which keeps keys ordered).
- **C#**: `JsonSerializerOptions` does not order; order via `SortedDictionary<string, object>` before serializing.
- **PHP**: call `ksort($array)` before `json_encode($array)`.
- **Python**: `json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False)` - native, no extra configuration.
- **Go**: `json.Marshal` of a `map[string]interface{}` already guarantees alphabetical order since Go 1.12+.
- **JavaScript/Node**: `JSON.stringify` preserves insertion order; order the keys manually before serializing:
  ```javascript
  const sorted = Object.keys(obj).sort().reduce((acc, key) => { acc[key] = obj[key]; return acc; }, {});
  const body = JSON.stringify(sorted);
  ```
:::

::: warning Beware of JSON.stringify(obj, null, 2)
`JSON.stringify(obj)` (without indentation) produces `{"a":1,"b":2}` - no spaces, already normalized.

`JSON.stringify(obj, null, 2)` (with indentation for humans) produces:
```
{
  "a": 1,
  "b": 2
}
```
- with spaces after `:` and newlines. **This breaks the HMAC**. Always use `JSON.stringify(obj)` without indentation to compute and send the HMAC.
:::

::: danger Do not escape non-ASCII characters (accents)
The server canonicalizes the body as **raw UTF-8** (accents as UTF-8 bytes, no `\uXXXX`). Serializers that escape non-ASCII by default produce a signature that **does not match** when the payload contains accents (e.g. `"descrição"`, `"São Paulo"`):

- **Python**: use `ensure_ascii=False` (the `True` default escapes to `\uXXXX`).
- **PHP**: use `JSON_UNESCAPED_UNICODE` together with `JSON_UNESCAPED_SLASHES`.
- **C#**: `System.Text.Json` escapes non-ASCII (and even `<`, `>`, `&`, `+`) by default; set `Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping` and validate with an accented payload before going to production.
- **JavaScript/Java (Jackson)/Go**: already emit raw UTF-8 by default, nothing to do.
:::

::: tip HMAC-SHA512 vs Webhook SHA256
The API uses **HMAC-SHA512** to authenticate requests sent by you. Webhooks sent by Monetarie use **HMAC-SHA256** for signature (header `X-Monetarie-Signature`). They are different algorithms -- each in its context. See [Webhooks](/en/webhooks) for details on webhook validation.

Another important difference: in the **request HMAC-SHA512 (client → Monetarie direction)** the server reorders keys alphabetically before comparing, so the client must produce the body ordered. In the **webhook HMAC-SHA256 (Monetarie → client direction)** the client receives the body exactly as it was sent (no later reordering) and must validate the signature over that RAW body -- do not re-serialize before validating.
:::

## Required Headers

| Header | Value | Notes |
|--------|-------|-------|
| `Authorization` | `ApiKey {client_id}:{client_secret}` or `Basic {base64(client_id:client_secret)}` | Same credential, two accepted formats |
| `Content-Type` | `application/json` | `POST` without `Content-Type: application/json` → 415 before even reaching HMAC |
| `hmac` | HMAC-SHA512 signature in **lowercase hexadecimal** | 128 hex characters. If sent in uppercase, the server normalizes to lowercase before comparing -- but send in lowercase as a convention |

## Examples

### JavaScript (Node.js)

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

// Keys in alphabetical order (amount < description < pix_key < pix_key_type)
const payload = {
  amount: 3000,
  description: "Pagamento",
  pix_key: "12345678901",
  pix_key_type: "cpf"
};

// JSON.stringify preserves insertion order, it does NOT sort by key.
// Even if the object above is alphabetical, explicitly forcing sort
// guarantees that the body matches the hash expected by the server.
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_your-client-secret')
  .update(body)
  .digest('hex');

// Send `body` as request body and `hmac` in the hmac header.
```

### Python

```python
import hmac
import hashlib
import json

# sort_keys=True guarantees alphabetical order - MANDATORY
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_your-client-secret",
    body.encode("utf-8"),
    hashlib.sha512
).hexdigest()
```

### PHP

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

// ksort guarantees alphabetical key order - MANDATORY
ksort($data);
$body = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

$hmac = hash_hmac('sha512', $body, 'sk_your-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 keeps the keys in alphabetical order - MANDATORY
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_your-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 keeps the keys in alphabetical order - MANDATORY
// (JsonSerializerOptions does NOT sort keys by default)
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_your-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_your-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"
```

## Validation

The API recomputes the HMAC-SHA512 of the received body (after normalization) and compares it against the value received in the `hmac` header using constant-time comparison. All errors from the `HmacValidation` plug follow the format `{"worked": false, "detail": "..."}`:

### 401 -- Invalid signature or missing header

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

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

### 400 -- Missing body or invalid JSON

```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 without HMAC secret configured

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

::: tip HMAC integration checklist
- Use the body **exactly** as sent in the request (same JSON serialization, keys in **alphabetical order**, without spaces after `:` and `,`).
- The API Key `client_secret` is the HMAC key -- the same value sent in `Authorization: ApiKey {client_id}:{client_secret}` (or inside the `base64` when using `Authorization: Basic`).
- The signature must have **128 lowercase hexadecimal characters** (SHA-512 produces 64 bytes = 128 hex chars). The API normalizes the header to lowercase before comparing, but send in lowercase as a convention. Uppercase or mixed (e.g., `AbCdEf`) works but is discouraged.
- The comparison is done in constant time -- there is no way to discover the signature by timing.
- If your client sends `X-Key-Case: camelCase`, sign the body **in the form it is traveling on the wire** (camelCase). The HMAC always sees the body as you sent it, before any internal conversion -- see [Authentication -- Optional headers](/en/auth-token#headers).
:::
