How to Send HMAC Signatures with Axios

Securing API requests often requires cryptographic payload signatures, such as HMAC (Hash-based Message Authentication Code), to verify data integrity and authenticity. This guide explains the standard approach to generating HMAC signatures and transmitting them properly using the Axios HTTP client via custom request headers and automated interceptors.

The Standard Method: Custom Request Headers

The industry-standard method for transmitting payload signatures is through custom HTTP headers (e.g., X-Signature, X-HMAC-Signature, or X-Hub-Signature-256). Placing the signature in the header separates the authentication metadata from the actual request body.

Basic Implementation

To sign a request, serialize your payload, generate the HMAC hash using a shared secret key, and attach the signature to the Axios headers configuration.

const axios = require('axios');
const crypto = require('crypto');

const secretKey = 'your-shared-secret';
const payload = {
  userId: 12345,
  action: 'process_payment',
  amount: 99.99
};

// 1. Serialize the payload to a consistent JSON string
const requestBody = JSON.stringify(payload);

// 2. Generate the HMAC-SHA256 signature
const signature = crypto
  .createHmac('sha256', secretKey)
  .update(requestBody)
  .digest('hex');

// 3. Transmit via Axios headers
axios.post('https://api.example.com/endpoint', payload, {
  headers: {
    'Content-Type': 'application/json',
    'X-Signature': signature
  }
})
.then(response => console.log('Success:', response.data))
.catch(error => console.error('Error:', error));

Preventing Replay Attacks with Timestamps

A complete HMAC implementation should include a timestamp (and optionally a nonce) in the signature calculation to prevent replay attacks.

const timestamp = Math.floor(Date.now() / 1000).toString();
const stringToSign = `${timestamp}.${requestBody}`;

const signature = crypto
  .createHmac('sha256', secretKey)
  .update(stringToSign)
  .digest('hex');

axios.post('https://api.example.com/endpoint', payload, {
  headers: {
    'Content-Type': 'application/json',
    'X-Signature': signature,
    'X-Timestamp': timestamp
  }
});

Automating Signatures with Axios Interceptors

If you need to sign multiple requests, use an Axios request interceptor. This guarantees that every outgoing request automatically serializes its data and computes the signature before sending.

const axios = require('axios');
const crypto = require('crypto');

const apiClient = axios.create({
  baseURL: 'https://api.example.com'
});

apiClient.interceptors.request.use((config) => {
  if (config.data && (config.method === 'post' || config.method === 'put' || config.method === 'patch')) {
    const rawData = typeof config.data === 'string' ? config.data : JSON.stringify(config.data);
    const timestamp = Date.now().toString();

    // Set serialized data back to ensure the exact body is sent
    config.data = rawData;

    const signature = crypto
      .createHmac('sha256', process.env.API_SECRET)
      .update(`${timestamp}.${rawData}`)
      .digest('hex');

    config.headers['Content-Type'] = 'application/json';
    config.headers['X-Signature'] = signature;
    config.headers['X-Timestamp'] = timestamp;
  }

  return config;
}, (error) => {
  return Promise.reject(error);
});

// Requests made through apiClient will now be signed automatically
apiClient.post('/endpoint', { user: 'alice' });

Critical Considerations