HMAC Documentation

Complete guide for implementing HMAC-SHA512 signatures in multiple languages

HMAC Documentation

Summary

After obtaining your HMAC key, this documentation provides comprehensive instructions for generating HMAC (Hash-based Message Authentication Code) signatures using various programming languages. HMAC is required for utilizing the PIX Cash In and PIX Cash Out endpoints, ensuring data integrity and authenticity in HTTP requests.

HMAC authentication is a robust method to safeguard your API endpoints. It ensures data integrity and restricts access to authorized clients only. By implementing HMAC authentication, you can enhance the security of your API, confidently share your services globally, and keep malicious actors at bay.

🔒

Security: Properly configuring a script that formats the JSON payload correctly is essential for consistently generating HMACs.

How HMAC Works

Request Body (JSON) 
    ↓
Normalize JSON (remove spaces)
    ↓
Generate HMAC-SHA512 Hash with Secret Key
    ↓
Convert to Hexadecimal
    ↓
Include in Request Headers/Body

Quick Start

Basic Steps

  1. Prepare the Request Body

    • Serialize to JSON without pretty-printing
    • Remove spaces after : and , characters
    • Example: {"key":"value","number":123}
    • Not: {"key": "value", "number": 123}
  2. Generate HMAC Signature

    • Use HMAC-SHA512 algorithm
    • Use your secret hmac_key provided during registration
    • Output in hexadecimal format
  3. Include in Request

    • Add signature to request headers or body (check endpoint documentation)

Script Functionality

The HMAC generation process can be broken down into the following steps:

1. Import Cryptographic Library

Import the cryptographic library for your programming language.

2. Create generateHMAC Function

This function takes two parameters:

  • jsonData: The request body in JSON format
  • secretKey: The secret key (HMAC key) used to generate the HMAC

The function uses HMAC-SHA512 to generate the signature and returns the result in hexadecimal format.

3. Retrieve the Request Body

Get the request body as a raw string.

4. Parse the JSON

Convert the raw JSON string into a native object/dictionary.

5. Convert Back to JSON String

Convert the object back to a JSON string without formatting (no indentation, no pretty-printing).

6. Remove Unnecessary Spaces

Critical Step: Remove spaces after colons (:) and commas (,) in the JSON string. This ensures the HMAC is generated consistently, regardless of the original JSON formatting.

Before: {"key": "value", "number": 123}
After:  {"key":"value","number":123}

7. Retrieve the Secret Key

Use the HMAC key provided to you during the registration process.

📧

Note: Only after registering the HMAC key will it be possible to complete this step.

Example key: edcb3xxxxf248b744653f052b22cexxxx8d87ad2b2777xxxx35f33d27be6xxxx

Best Practice: Secure Key Storage

Never include the secret key directly in your code. Retrieve it from a secure source such as environment variables or a secret management system.

8. Generate the HMAC

Call the generateHMAC function with the formatted request body and secret key, storing the generated HMAC in a variable.

9. Store or Use the HMAC

Store the generated HMAC in a variable for use in API requests, or log it for debugging purposes.

Complete Implementation Examples

JavaScript (Postman)

// Function to generate HMAC
function generateHMAC(jsonData, secretKey) {
    const crypto = require('crypto-js');
    const hmacSHA512 = crypto.HmacSHA512(jsonData, secretKey);
    return hmacSHA512.toString(crypto.enc.Hex);
}

// Get the request body
let payload = pm.request.body.raw;

// Parse the JSON string
let jsonObj = JSON.parse(payload);

// Convert back to a JSON string without pretty-printing
payload = JSON.stringify(jsonObj);

// Replace ": " with ":" and ", " with ","
payload = payload.replace(/:\s/g, ':').replace(/,\s/g, ',');

// Get the secret key (you need to set this in your environment variables)
const secretKey = "edcb3xxxxf248b744653f052b22cexxxx8d87ad2b2777xxxx35f33d27be6xxxx";
// Best practice: const secretKey = pm.environment.get('hmac_key');

// Generate the HMAC
const hmac = generateHMAC(payload, secretKey);

// Set the HMAC in an environment variable for later use
pm.collectionVariables.set('generated_hmac', hmac);

console.log('Generated HMAC:', hmac);

Postman Environment Variables Setup:

// Get the secret key from Postman environment variables
const secretKey = pm.environment.get('hmac_key');

// In Postman, it is a best practice to store sensitive information like 
// secret keys in environment variables. You can set these variables in the 
// Postman interface and retrieve them securely in your scripts.

Python

import hashlib
import hmac
import json


def gera_hash(json_data, chave_secreta):
    """Generate HMAC-SHA512 hash for API request authentication."""
    chave_secreta = chave_secreta.encode('utf-8')
    hmac_sha512 = hmac.new(chave_secreta, json_data, hashlib.sha512)
    chave_hmac = hmac_sha512.hexdigest()
    return chave_hmac


# Get the request body (assuming it's a JSON string)
payload = '{"key1": "value1", "key2": "value2"}'

# Normalize JSON formatting
payload_json_str = json.dumps(json.loads(payload), ensure_ascii=False, separators=(',', ':'))
payload_json_str = payload_json_str.encode('utf-8')

# Secret key (use environment variable in production)
secret_key = "7a3edd380d2356bcb5f4bb1ff3a25f918cfb11e83c2eb468f967457037e43949"
# Best practice: secret_key = os.getenv('HMAC_KEY')

# Generate HMAC
hmac_result = gera_hash(payload_json_str, secret_key)
print("Generated HMAC:", hmac_result)

Best Practice - Using Environment Variables:

import os
import hmac
import hashlib
import json


def generate_hmac(json_data, secret_key):
    """Generate HMAC-SHA512 hash."""
    secret_bytes = secret_key.encode('utf-8')
    hmac_hash = hmac.new(secret_bytes, json_data, hashlib.sha512)
    return hmac_hash.hexdigest()


# Load secret key from environment
secret_key = os.getenv('HMAC_KEY')
if not secret_key:
    raise ValueError("HMAC_KEY environment variable not set")

# Prepare payload
payload = {"key1": "value1", "key2": "value2"}
payload_str = json.dumps(payload, ensure_ascii=False, separators=(',', ':'))
payload_bytes = payload_str.encode('utf-8')

# Generate HMAC
hmac_signature = generate_hmac(payload_bytes, secret_key)
print(f"Generated HMAC: {hmac_signature}")

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Formatter;

public class HMACGenerator {

    public static String generateHMAC(String jsonData, String secretKey) throws Exception {
        Mac sha512_HMAC = Mac.getInstance("HmacSHA512");
        SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA512");
        sha512_HMAC.init(keySpec);
        
        byte[] hmacBytes = sha512_HMAC.doFinal(jsonData.getBytes(StandardCharsets.UTF_8));
        
        return bytesToHex(hmacBytes);
    }

    private static String bytesToHex(byte[] bytes) {
        Formatter formatter = new Formatter();
        for (byte b : bytes) {
            formatter.format("%02x", b);
        }
        return formatter.toString();
    }

    public static void main(String[] args) throws Exception {
        // Payload (already normalized)
        String payload = "{\"key1\":\"value1\",\"key2\":\"value2\"}";

        // Secret key (use environment variable in production)
        String secretKey = "edcb3xxxxf248b744653f052b22cexxxx8d87ad2b2777xxxx35f33d27be6xxxx";
        // Best practice: String secretKey = System.getenv("HMAC_KEY");

        // Generate the HMAC
        String hmacResult = generateHMAC(payload, secretKey);

        System.out.println("Generated HMAC: " + hmacResult);
    }
}

Best Practice - Using Environment Variables:

public class SecureHMACGenerator {
    
    public static void main(String[] args) throws Exception {
        // Load secret key from environment
        String secretKey = System.getenv("HMAC_KEY");
        if (secretKey == null || secretKey.isEmpty()) {
            throw new IllegalStateException("HMAC_KEY environment variable not set");
        }
        
        // Prepare payload
        String payload = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
        
        // Generate HMAC
        String hmacSignature = generateHMAC(payload, secretKey);
        System.out.println("Generated HMAC: " + hmacSignature);
    }
    
    // generateHMAC method same as above
}

JavaScript (Node.js)

const crypto = require('crypto');

function generateHMAC(jsonData, secretKey) {
    const hmac = crypto.createHmac('sha512', secretKey);
    hmac.update(jsonData);
    return hmac.digest('hex');
}

// Get the request body
let payload = '{"key1": "value1", "key2": "value2"}';

// Parse the JSON string
let jsonObj = JSON.parse(payload);

// Convert back to a JSON string without pretty-printing
payload = JSON.stringify(jsonObj);

// Replace ": " with ":" and ", " with ","
payload = payload.replace(/:\s/g, ':').replace(/,\s/g, ',');

// Secret key (use environment variable in production)
const secretKey = "edcb3xxxxf248b744653f052b22cexxxx8d87ad2b2777xxxx35f33d27be6xxxx";
// Best practice: const secretKey = process.env.HMAC_KEY;

// Generate the HMAC
const hmac = generateHMAC(payload, secretKey);

console.log('Generated HMAC:', hmac);

Best Practice - Environment Variables & Error Handling:

require('dotenv').config();  // Load .env file
const crypto = require('crypto');

function generateHMAC(jsonData, secretKey) {
    if (!secretKey) {
        throw new Error('HMAC_KEY not configured');
    }
    
    const hmac = crypto.createHmac('sha512', secretKey);
    hmac.update(jsonData);
    return hmac.digest('hex');
}

function normalizeJSON(payload) {
    // Parse and stringify to normalize
    const jsonObj = JSON.parse(payload);
    const normalized = JSON.stringify(jsonObj);
    // Remove spaces after : and ,
    return normalized.replace(/:\s/g, ':').replace(/,\s/g, ',');
}

// Load secret from environment
const secretKey = process.env.HMAC_KEY;

if (!secretKey) {
    throw new Error('HMAC_KEY environment variable is not set');
}

// Example usage
const payload = {"key1": "value1", "key2": "value2"};
const payloadStr = normalizeJSON(JSON.stringify(payload));

const hmacSignature = generateHMAC(payloadStr, secretKey);
console.log('Generated HMAC:', hmacSignature);

module.exports = { generateHMAC, normalizeJSON };

C#

using System;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

class Program
{
    static void Main()
    {
        string payload = "{\"key1\":\"value1\",\"key2\":\"value2\"}";

        // Parse the JSON string
        JObject jsonObj = JObject.Parse(payload);

        // Convert back to a JSON string without pretty-printing
        payload = JsonConvert.SerializeObject(jsonObj, Formatting.None);

        // Secret key (use environment variable in production)
        string secretKey = "edcb3xxxxf248b744653f052b22cexxxx8d87ad2b2777xxxx35f33d27be6xxxx";
        // Best practice: string secretKey = Environment.GetEnvironmentVariable("HMAC_KEY");

        // Generate the HMAC
        string hmac = GenerateHMAC(payload, secretKey);

        Console.WriteLine("Generated HMAC: " + hmac);
    }

    static string GenerateHMAC(string jsonData, string secretKey)
    {
        using (var hmacsha512 = new HMACSHA512(Encoding.UTF8.GetBytes(secretKey)))
        {
            byte[] hashmessage = hmacsha512.ComputeHash(Encoding.UTF8.GetBytes(jsonData));
            return BitConverter.ToString(hashmessage).Replace("-", "").ToLower();
        }
    }
}

Best Practice - Environment Variables & Error Handling:

using System;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public class SecureHMACGenerator
{
    public static string GenerateHMAC(string jsonData, string secretKey)
    {
        if (string.IsNullOrEmpty(secretKey))
        {
            throw new ArgumentException("Secret key cannot be null or empty", nameof(secretKey));
        }

        using (var hmacsha512 = new HMACSHA512(Encoding.UTF8.GetBytes(secretKey)))
        {
            byte[] hashmessage = hmacsha512.ComputeHash(Encoding.UTF8.GetBytes(jsonData));
            return BitConverter.ToString(hashmessage).Replace("-", "").ToLower();
        }
    }

    static void Main()
    {
        // Load secret key from environment
        string secretKey = Environment.GetEnvironmentVariable("HMAC_KEY");
        if (string.IsNullOrEmpty(secretKey))
        {
            throw new InvalidOperationException("HMAC_KEY environment variable not set");
        }

        // Prepare payload
        var payload = new { key1 = "value1", key2 = "value2" };
        string payloadStr = JsonConvert.SerializeObject(payload, Formatting.None);

        // Generate HMAC
        string hmacSignature = GenerateHMAC(payloadStr, secretKey);
        Console.WriteLine($"Generated HMAC: {hmacSignature}");
    }
}

PHP

<?php

function generateHMAC($jsonData, $secretKey) {
    return hash_hmac('sha512', $jsonData, $secretKey);
}

// Get the request body
$payload = '{"key1":"value1","key2":"value2"}';

// Decode and encode JSON to remove pretty-printing
$jsonObj = json_decode($payload, true);
$payload = json_encode($jsonObj, JSON_UNESCAPED_SLASHES);

// Secret key (use environment variable in production)
$secretKey = "edcb3xxxxf248b744653f052b22cexxxx8d87ad2b2777xxxx35f33d27be6xxxx";
// Best practice: $secretKey = getenv('HMAC_KEY');

// Generate the HMAC
$hmac = generateHMAC($payload, $secretKey);

echo "Generated HMAC: " . $hmac;
?>

Best Practice - Environment Variables & Error Handling:

<?php

function generateHMAC($jsonData, $secretKey) {
    if (empty($secretKey)) {
        throw new Exception('Secret key is required');
    }
    return hash_hmac('sha512', $jsonData, $secretKey);
}

function normalizeJSON($payload) {
    // Decode and encode to normalize
    $jsonObj = json_decode($payload, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new Exception('Invalid JSON: ' . json_last_error_msg());
    }
    return json_encode($jsonObj, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}

// Load secret key from environment
$secretKey = getenv('HMAC_KEY');

if ($secretKey === false || empty($secretKey)) {
    throw new Exception('HMAC_KEY environment variable not set');
}

// Example usage
$payload = ['key1' => 'value1', 'key2' => 'value2'];
$payloadStr = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

$hmacSignature = generateHMAC($payloadStr, $secretKey);
echo "Generated HMAC: " . $hmacSignature . "\n";
?>

Common Issues and Solutions

Issue 1: HMAC Signature Mismatch

Symptom: API returns 403 Forbidden with "Invalid HMAC signature" error.

Causes & Solutions:

  1. Spaces in JSON:

    // ❌ Wrong
    {"key": "value", "number": 123}
    
    // ✅ Correct
    {"key":"value","number":123}

    Solution: Use .replace(/:\s/g, ':').replace(/,\s/g, ',')

  2. Pretty-printed JSON:

    // ❌ Wrong
    {
      "key": "value"
    }
    
    // ✅ Correct
    {"key":"value"}

    Solution: Use JSON.stringify() without formatting options

  3. Wrong Algorithm:

    • Must use HMAC-SHA512, not SHA256 or other algorithms
  4. Wrong Secret Key:

    • Verify you're using the correct hmac_key
    • Check for trailing spaces or hidden characters
  5. Request Body Modified:

    • Ensure the request body sent to API matches the signed payload exactly

Issue 2: Environment Variable Not Loaded

Symptom: Application crashes with "undefined" or "null" secret key error.

Solutions:

Node.js:

require('dotenv').config();
const secretKey = process.env.HMAC_KEY;
if (!secretKey) throw new Error('HMAC_KEY not set');

Python:

import os
from dotenv import load_dotenv

load_dotenv()
secret_key = os.getenv('HMAC_KEY')
if not secret_key:
    raise ValueError('HMAC_KEY not set')

PHP:

$secretKey = getenv('HMAC_KEY');
if (!$secretKey) {
    throw new Exception('HMAC_KEY environment variable not set');
}

Issue 3: Unicode and Special Characters

Symptom: HMAC works for simple requests but fails with special characters.

Solution: Ensure proper encoding:

Python:

# Use ensure_ascii=False for Unicode support
payload_str = json.dumps(payload, ensure_ascii=False, separators=(',', ':'))

PHP:

// Use JSON_UNESCAPED_UNICODE
$payload = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

JavaScript:

// JSON.stringify handles Unicode correctly by default
const payload = JSON.stringify(data);

Testing Your Implementation

Test Payload

{
  "amount": 100.50,
  "description": "Test payment",
  "customer_id": "12345"
}

Expected Normalized Format

{"amount":100.5,"description":"Test payment","customer_id":"12345"}

Test Secret Key

test_secret_key_123456789

Expected HMAC (SHA512 hex)

[Calculate using your implementation and verify it matches across all platforms]

Validation Script

// Node.js validation
const crypto = require('crypto');

function testHMAC() {
    const payload = '{"amount":100.5,"description":"Test payment","customer_id":"12345"}';
    const secretKey = 'test_secret_key_123456789';
    
    const hmac = crypto.createHmac('sha512', secretKey);
    hmac.update(payload);
    const result = hmac.digest('hex');
    
    console.log('HMAC Result:', result);
    return result;
}

const expectedHMAC = testHMAC();
// Compare this result across all your implementations

Security Best Practices

1. Never Hardcode Secrets

// ❌ NEVER do this
const HMAC_KEY = "edcb3xxxxf248b744653f052b22cexxxx";

// ✅ Always use environment variables
const HMAC_KEY = process.env.HMAC_KEY;

2. Use Secure Storage

  • Development: .env files (add to .gitignore)
  • Production: AWS Secrets Manager, Azure Key Vault, HashiCorp Vault
  • CI/CD: Encrypted environment variables

3. Rotate Keys Regularly

  • Implement key rotation every 90 days
  • Support multiple active keys during rotation period
  • Revoke old keys after transition

4. Log Safely

// ❌ Never log the secret key
console.log('Secret Key:', secretKey);

// ❌ Never log the HMAC signature in production
console.log('HMAC:', hmacSignature);

// ✅ Log only success/failure
console.log('HMAC generated successfully');

5. Validate Input

def generate_hmac(payload, secret_key):
    if not payload:
        raise ValueError("Payload cannot be empty")
    if not secret_key:
        raise ValueError("Secret key is required")
    if not isinstance(payload, (str, bytes)):
        raise TypeError("Payload must be string or bytes")
    
    # Generate HMAC...

Next Steps

  1. Implement HMAC generation in your preferred language using the examples above
  2. Test your implementation with the provided test payload
  3. Integrate with API requests - Include HMAC signature in all PIX Cash In/Out requests
  4. Review Auth Token endpoint - Combine Bearer token + HMAC for complete authentication

Related Documentation