SAQ DigitalDocs

    Authentication

    Learn how to authenticate your API requests using Bearer tokens and HMAC signatures

    The API uses a two-layer authentication system combining Bearer tokens and HMAC signatures to ensure secure communication.

    Authentication Methods

    1. Bearer Token (Required)

    The Bearer token identifies your application and user session.

    2. HMAC Signature (Required for API Audience)

    The HMAC signature verifies request integrity and authenticity.

    Getting Your Credentials

    1. Log in to your dashboard
    2. Navigate to Settings → API Keys
    3. Generate a new API key
    4. Save your credentials:
      • Access Token: For the Authorization header
      • Secret Key: For generating HMAC signatures

    ⚠️ Security Warning: Never expose your credentials in client-side code or commit them to version control.

    Authentication Headers

    Every API request must include:

    Authorization: Bearer {access_token}
    hmac: {hmac_signature}
    Content-Type: application/json

    Audience Types

    The API supports two audience types:

    API Audience

    For server-to-server communication:

    • Required: Bearer token + HMAC signature
    • Use Case: Backend integrations, automated processes
    • Security: Highest level (two-factor authentication)

    WEB Audience

    For user-authenticated requests:

    • Required: Bearer token + (HMAC or MFA token)
    • Use Case: Web applications, mobile apps with user login
    • Additional: May require MFA token for sensitive operations

    Generating HMAC Signatures

    HMAC Message Format

    {HTTP_METHOD}{REQUEST_PATH}{JSON_BODY}

    Rules:

    1. HTTP Method: Uppercase (GET, POST, PUT, DELETE)
    2. Request Path: Full path including query parameters
    3. JSON Body: Compact JSON (no spaces) for POST/PUT requests

    Examples:

    GET Request:

    Message: GET/v3/account/5001/balance

    POST Request:

    Message: POST/v3/cashout/create-cashout{"amount":100.50,"key":"user@example.com"}

    GET with Query Parameters:

    Message: GET/v3/account/list-my-pix-keys?account_branch_identifier=0001&account_number=123456

    Implementation Examples

    Python

    import hmac
    import hashlib
    import json
    
    def generate_hmac(secret_key, method, path, body=None):
        """
        Generate HMAC signature for API request.
        
        Args:
            secret_key: Your API secret key
            method: HTTP method (GET, POST, PUT, DELETE)
            path: Request path including query parameters
            body: Request body dict (for POST/PUT)
        
        Returns:
            HMAC signature (hexadecimal string)
        """
        # Build message
        message = f"{method.upper()}{path}"
        
        # Add body for POST/PUT requests
        if body:
            # Compact JSON (no spaces)
            message += json.dumps(body, separators=(',', ':'))
        
        # Generate HMAC-SHA256
        signature = hmac.new(
            secret_key.encode('utf-8'),
            message.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        
        return signature
    
    
    # Example usage
    secret_key = "your_secret_key"
    method = "GET"
    path = "/v3/account/5001/balance"
    
    hmac_sig = generate_hmac(secret_key, method, path)
    print(f"HMAC: {hmac_sig}")
    
    # Example with POST body
    method = "POST"
    path = "/v3/cashout/create-cashout"
    body = {
        "amount": 100.50,
        "key": "user@example.com",
        "source_account_branch_identifier": "0001",
        "source_account_number": "123456"
    }
    
    hmac_sig = generate_hmac(secret_key, method, path, body)
    print(f"HMAC: {hmac_sig}")

    JavaScript (Node.js)

    const crypto = require('crypto');
    
    function generateHMAC(secretKey, method, path, body = null) {
      /**
       * Generate HMAC signature for API request.
       * 
       * @param {string} secretKey - Your API secret key
       * @param {string} method - HTTP method (GET, POST, PUT, DELETE)
       * @param {string} path - Request path including query parameters
       * @param {Object} body - Request body object (for POST/PUT)
       * @returns {string} HMAC signature (hexadecimal string)
       */
      
      // Build message
      let message = `${method.toUpperCase()}${path}`;
      
      // Add body for POST/PUT requests
      if (body) {
        // Compact JSON (no spaces)
        message += JSON.stringify(body);
      }
      
      // Generate HMAC-SHA256
      const signature = crypto
        .createHmac('sha256', secretKey)
        .update(message, 'utf8')
        .digest('hex');
      
      return signature;
    }
    
    // Example usage
    const secretKey = 'your_secret_key';
    const method = 'GET';
    const path = '/v3/account/5001/balance';
    
    const hmacSig = generateHMAC(secretKey, method, path);
    console.log(`HMAC: ${hmacSig}`);
    
    // Example with POST body
    const postMethod = 'POST';
    const postPath = '/v3/cashout/create-cashout';
    const body = {
      amount: 100.50,
      key: 'user@example.com',
      source_account_branch_identifier: '0001',
      source_account_number: '123456'
    };
    
    const postHmacSig = generateHMAC(secretKey, postMethod, postPath, body);
    console.log(`HMAC: ${postHmacSig}`);

    PHP

    <?php
    
    function generateHMAC($secretKey, $method, $path, $body = null) {
        /**
         * Generate HMAC signature for API request.
         * 
         * @param string $secretKey Your API secret key
         * @param string $method HTTP method (GET, POST, PUT, DELETE)
         * @param string $path Request path including query parameters
         * @param array $body Request body array (for POST/PUT)
         * @return string HMAC signature (hexadecimal string)
         */
        
        // Build message
        $message = strtoupper($method) . $path;
        
        // Add body for POST/PUT requests
        if ($body !== null) {
            // Compact JSON (no spaces)
            $message .= json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }
        
        // Generate HMAC-SHA256
        $signature = hash_hmac('sha256', $message, $secretKey);
        
        return $signature;
    }
    
    // Example usage
    $secretKey = 'your_secret_key';
    $method = 'GET';
    $path = '/v3/account/5001/balance';
    
    $hmacSig = generateHMAC($secretKey, $method, $path);
    echo "HMAC: $hmacSig\n";
    
    // Example with POST body
    $postMethod = 'POST';
    $postPath = '/v3/cashout/create-cashout';
    $body = [
        'amount' => 100.50,
        'key' => 'user@example.com',
        'source_account_branch_identifier' => '0001',
        'source_account_number' => '123456'
    ];
    
    $postHmacSig = generateHMAC($secretKey, $postMethod, $postPath, $body);
    echo "HMAC: $postHmacSig\n";

    Java

    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.nio.charset.StandardCharsets;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import com.google.gson.Gson;
    import java.util.Map;
    
    public class HMACGenerator {
        
        public static String generateHMAC(String secretKey, String method, String path, Map<String, Object> body) 
                throws NoSuchAlgorithmException, InvalidKeyException {
            /**
             * Generate HMAC signature for API request.
             * 
             * @param secretKey Your API secret key
             * @param method HTTP method (GET, POST, PUT, DELETE)
             * @param path Request path including query parameters
             * @param body Request body map (for POST/PUT, null for GET/DELETE)
             * @return HMAC signature (hexadecimal string)
             */
            
            // Build message
            StringBuilder message = new StringBuilder();
            message.append(method.toUpperCase()).append(path);
            
            // Add body for POST/PUT requests
            if (body != null) {
                Gson gson = new Gson();
                message.append(gson.toJson(body));
            }
            
            // Generate HMAC-SHA256
            Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(
                secretKey.getBytes(StandardCharsets.UTF_8), 
                "HmacSHA256"
            );
            hmacSHA256.init(secretKeySpec);
            
            byte[] signatureBytes = hmacSHA256.doFinal(
                message.toString().getBytes(StandardCharsets.UTF_8)
            );
            
            // Convert to hex string
            StringBuilder signature = new StringBuilder();
            for (byte b : signatureBytes) {
                signature.append(String.format("%02x", b));
            }
            
            return signature.toString();
        }
        
        // Example usage
        public static void main(String[] args) {
            try {
                String secretKey = "your_secret_key";
                String method = "GET";
                String path = "/v3/account/5001/balance";
                
                String hmacSig = generateHMAC(secretKey, method, path, null);
                System.out.println("HMAC: " + hmacSig);
                
                // Example with POST body
                Map<String, Object> body = Map.of(
                    "amount", 100.50,
                    "key", "user@example.com",
                    "source_account_branch_identifier", "0001",
                    "source_account_number", "123456"
                );
                
                String postHmacSig = generateHMAC(secretKey, "POST", "/v3/cashout/create-cashout", body);
                System.out.println("POST HMAC: " + postHmacSig);
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    Complete Request Examples

    GET Request Example

    import requests
    import hmac
    import hashlib
    
    API_BASE = "https://api.saq.digital"
    ACCESS_TOKEN = "your_access_token"
    SECRET_KEY = "your_secret_key"
    
    # Request parameters
    account_id = 5001
    method = "GET"
    path = f"/v3/account/{account_id}/balance"
    
    # Generate HMAC
    message = f"{method}{path}"
    hmac_signature = hmac.new(
        SECRET_KEY.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()
    
    # Make request
    headers = {
        "Authorization": f"Bearer {ACCESS_TOKEN}",
        "hmac": hmac_signature,
        "Content-Type": "application/json"
    }
    
    response = requests.get(f"{API_BASE}{path}", headers=headers)
    print(response.json())

    POST Request Example

    import requests
    import hmac
    import hashlib
    import json
    
    API_BASE = "https://api.saq.digital"
    ACCESS_TOKEN = "your_access_token"
    SECRET_KEY = "your_secret_key"
    
    # Request parameters
    method = "POST"
    path = "/v3/cashout/create-cashout"
    body = {
        "source_account_branch_identifier": "0001",
        "source_account_number": "123456",
        "amount": 100.50,
        "key": "recipient@example.com"
    }
    
    # Generate HMAC
    message = f"{method}{path}{json.dumps(body, separators=(',', ':'))}"
    hmac_signature = hmac.new(
        SECRET_KEY.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()
    
    # Make request
    headers = {
        "Authorization": f"Bearer {ACCESS_TOKEN}",
        "hmac": hmac_signature,
        "Content-Type": "application/json"
    }
    
    response = requests.post(f"{API_BASE}{path}", headers=headers, json=body)
    print(response.json())

    Common Authentication Errors

    401 Unauthorized

    Cause: Invalid or expired access token

    Solution:

    1. Verify your access token is correct
    2. Check if token has expired
    3. Generate a new token from the dashboard

    403 Forbidden - Invalid HMAC

    Cause: HMAC signature mismatch

    Common Issues:

    1. Wrong message format: Ensure {METHOD}{PATH}{BODY}
    2. Spaces in JSON: Body must be compact JSON
    3. Wrong path: Include full path with query parameters
    4. Wrong secret key: Verify you're using the correct secret
    5. Encoding issues: Ensure UTF-8 encoding

    Debug Steps:

    # Print the message being signed
    message = f"{method}{path}"
    if body:
        message += json.dumps(body, separators=(',', ':'))
    print(f"Message to sign: {message}")
    print(f"HMAC: {hmac_signature}")

    403 Forbidden - Insufficient Permissions

    Cause: User doesn't have required permissions

    Solution:

    1. Verify user has necessary permissions in dashboard
    2. Check if account belongs to your company
    3. Confirm policy access for the specific resource

    MFA Token (WEB Audience)

    For sensitive operations with WEB audience, you may need an MFA (Multi-Factor Authentication) token:

    Operations Requiring MFA:

    • Registering CPF/CNPJ/EVP PIX keys
    • Approving cashout transactions (in some configurations)
    • Modifying security settings

    MFA Flow:

    1. User has MFA enabled in their account
    2. User generates TOTP code from authenticator app
    3. Include code in token field of request body

    Example:

    {
      "source_account_branch_identifier": "0001",
      "source_account_number": "123456",
      "reason": "Register CPF key",
      "type": "CPF",
      "token": "123456"
    }

    OTP Verification (EMAIL/CELL_PHONE Keys)

    For EMAIL and CELL_PHONE PIX keys:

    Verification Flow:

    1. Request OTP code: Call /v3/key/verifications/send
    2. Receive verification_request_id
    3. User receives code via email/SMS
    4. Include code and verification_request_id in registration request

    Example:

    {
      "source_account_branch_identifier": "0001",
      "source_account_number": "123456",
      "reason": "Register email key",
      "type": "EMAIL",
      "verification_request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "token": "123456"
    }

    Best Security Practices

    1. Store Credentials Securely

    # ✅ Good: Use environment variables
    import os
    ACCESS_TOKEN = os.getenv('API_ACCESS_TOKEN')
    SECRET_KEY = os.getenv('API_SECRET_KEY')
    
    # ❌ Bad: Hardcode credentials
    ACCESS_TOKEN = 'abc123...'  # Never do this!

    2. Never Log Sensitive Data

    # ✅ Good: Log without credentials
    logger.info(f"API request: {method} {path}")
    
    # ❌ Bad: Log credentials
    logger.info(f"Token: {access_token}")  # Never do this!

    3. Use HTTPS Only

    # ✅ Good: HTTPS
    API_BASE = "https://api.saq.digital"
    
    # ❌ Bad: HTTP (insecure)
    API_BASE = "http://api.example.com"  # Never do this!

    4. Rotate Credentials Regularly

    • Rotate API keys every 90 days
    • Immediately rotate if compromised
    • Use separate keys for different environments

    5. Implement Rate Limiting

    import time
    from functools import wraps
    
    def rate_limit(calls_per_second=10):
        min_interval = 1.0 / calls_per_second
        last_called = [0.0]
        
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                elapsed = time.time() - last_called[0]
                left_to_wait = min_interval - elapsed
                if left_to_wait > 0:
                    time.sleep(left_to_wait)
                ret = func(*args, **kwargs)
                last_called[0] = time.time()
                return ret
            return wrapper
        return decorator
    
    @rate_limit(calls_per_second=10)
    def make_api_call():
        # Your API call here
        pass

    Testing Authentication

    Test HMAC Generation

    def test_hmac_generation():
        secret_key = "test_key"
        method = "GET"
        path = "/v3/account/123/balance"
        
        expected_message = "GET/v3/account/123/balance"
        signature = generate_hmac(secret_key, method, path)
        
        print(f"Message: {expected_message}")
        print(f"Signature: {signature}")
        
        # Verify signature is 64 characters (SHA-256 hex)
        assert len(signature) == 64
        print("✓ HMAC generation working correctly")
    
    test_hmac_generation()

    Next Steps


    Security Tip: If you suspect your credentials have been compromised, immediately rotate your keys from the dashboard and contact your support channel.