HMAC Verification Code Example

The following Node.js example shows how to read the raw request body, calculate the expected HMAC signature, and compare it with the signature received in the request headers.

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

const secret = process.env.EHI_HMAC_SECRET;
const port = Number(process.env.PORT || 8085);

function sha256Hex(buffer) {
  return crypto.createHash('sha256').update(buffer).digest('hex');
}

http
  .createServer((req, res) => {
    const chunks = [];
    const receivedAt = new Date().toISOString();

    req.on('data', (chunk) => chunks.push(chunk));

    req.on('end', () => {
      const rawBodyBuffer = Buffer.concat(chunks);
      const rawBody = rawBodyBuffer.toString('utf8');

      const signatureTimestamp = req.headers['x-ehi-signature-timestamp'];
      const receivedSignature = req.headers['x-ehi-signature'];
      const algorithm = req.headers['x-ehi-signature-algorithm'];

      const payloadToSignBuffer = Buffer.concat([
        Buffer.from(`${signatureTimestamp}.`, 'utf8'),
        rawBodyBuffer,
      ]);

      const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(payloadToSignBuffer)
        .digest('hex');

      const report = {
        receivedAt,
        request: {
          method: req.method,
          url: req.url,
          httpVersion: req.httpVersion,
          remoteAddress: req.socket.remoteAddress,
          remotePort: req.socket.remotePort,
          headers: req.headers,
          rawHeaders: req.rawHeaders,
        },
        body: {
          rawBody,
          byteLength: rawBodyBuffer.length,
          sha256: sha256Hex(rawBodyBuffer),
          base64: rawBodyBuffer.toString('base64'),
        },
        hmac: {
          algorithm,
          timestamp: signatureTimestamp,
          receivedSignature,
          expectedSignature,
          matches: receivedSignature === expectedSignature,
          payloadToSignByteLength: payloadToSignBuffer.length,
          payloadToSignSha256: sha256Hex(payloadToSignBuffer),
          secretByteLength: Buffer.byteLength(secret || '', 'utf8'),
          secretFingerprintSha256: sha256Hex(
            Buffer.from(secret || '', 'utf8'),
          ).slice(0, 16),
        },
      };

      console.log('\n================ EHI REQUEST ================');
      console.dir(report, { depth: null, colors: true });
      console.log('=============================================\n');

      res.writeHead(report.hmac.matches ? 200 : 401, {
        'content-type': 'application/json',
      });

      res.end(
        JSON.stringify({
          Acknowledgement: '1',
          Responsestatus: '00',
        }),
      );
    });
  })
  .listen(port, '127.0.0.1', () => {
    console.log(`Mock sponsor on http://127.0.0.1:${port}/ehi`);
  });