SocredDocs

    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