Signature Verification

Webhook Signatures

  1. Setting Up a Webhook URL:

    • When you setup a webhook URL for a terminal using the endpoint /Terminals/{id}/set-webhook-url, BeadPay generates a new signingSecret in base64 format.

    • This signingSecret is included in the HTTP response.

  2. Receiving a Webhook Request:

    • When BeadPay sends a webhook request to your specified URL, it includes an x-webhook-signature header in the request.

    • The x-webhook-signature header contains the timestamp of the request and the signature, formatted as t=<timestamp>,s=<base64 signature>.

  3. Preparing for Signature Verification:

    • To begin the verification process, extract the timestamp (t) and the base64 signature (s) from the x-webhook-signature header.

    • Prepare a concatenated string using the extracted timestamp and the raw body of the request. The format should be <timestamp>.<raw body>.

  4. Generating a Signature for Verification:

    • Decode the base64 formatted signingSecret that you received when setting up the webhook.

    • Using a suitable cryptographic library in your programming environment, generate a new HMAC-SHA256 signature using the decoded signingSecret as the key and the concatenated string from step 3 as the message.

  5. Comparing the Signatures:

    • Compare the HMAC-SHA256 signature you generated with the base64 signature (s) extracted from the x-webhook-signature header.

    • If the two signatures match, it confirms the integrity of the request and the authenticity of the sender.

    • If they do not match, the request should be considered tampered with or not from BeadPay, and appropriate action should be taken.

Node.js examples

Webhook receiver

This code snippet creates a Node.js server that acts as a Webhook Receiver. It listens for incoming webhook requests at /webhook, verifies the HMAC SHA256 signature to ensure the requests are from a trusted source, and processes the valid requests.

const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');

const app = express();
const port = 3000;

function generateSignatureBase64(secretKeyBase64, message) {
  const secretKey = Buffer.from(secretKeyBase64, 'base64');
  return crypto
    .createHmac('sha256', secretKey)
    .update(message)
    .digest('base64');
}

// Secret key for HMAC verification
const secretKeyBase64 = 'QUFBQUFBQUFBQUFBQUFBQQ=='; // Replace with your base64 key

// Middleware to verify HMAC SHA256 signature
const verifyHmacSignature = (req, res, next) => {
  const signatureHeader = req.get("X-Webhook-Signature"); 
  const signatureHeaderFormatRegex = /t=(\d+),s=([^,]+)/;
  const formatMatches = signatureHeader.match(signatureHeaderFormatRegex);

  if (!formatMatches) {
    return res.status(400).send('Invalid signature format');
  }

  const timestamp = formatMatches[1];
  const signatureBase64 = formatMatches[2];
  
  const verifySignatureBase64 = generateSignatureBase64(secretKeyBase64, `${timestamp}.${req.body}`);

  if (verifySignatureBase64 === signatureBase64) {
    next();
  } else {
    res.status(403).send('Invalid signature');
  }
};

// Use body-parser middleware to parse JSON bodies as plain text
app.use(bodyParser.text({type:"*/json"}));

// Webhook endpoint with HMAC verification
app.post('/webhook', verifyHmacSignature, (req, res) => {
  console.log('Received valid webhook:', req.body);
  res.status(200).send();
});

app.listen(port, () => {
  console.log(`Server is listening on port ${port}`);
});

Webhook sender

If you want to test your Webhook Receiver, here is a Node.js example that simulates sending webhook requests. The script creates a request with an HMAC signature and sends it to the specified webhook endpoint (in this case, http://localhost:3000/webhook). The receiver can then validate this request to ensure its Webhook Receiver is functioning correctly.

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

function generateSignatureBase64(secretKeyBase64, content) {
  const secretKey = Buffer.from(secretKeyBase64, 'base64');
  return crypto
    .createHmac('sha256', secretKey)
    .update(content)
    .digest('base64');
}

// Secret key for HMAC signing
const secretKeyBase64 = 'QUFBQUFBQUFBQUFBQUFBQQ=='; // Replace with your base64 key

(async () => {
  const timestamp = '1705694230088';
  const requestBody = '{"dummy":"body"}';
  
  const signatureBase64 = generateSignatureBase64(secretKeyBase64, `${timestamp}.${requestBody}`);
  const signatureHeader = `t=${timestamp},s=${signatureBase64}`;
  
  const response = await axios.post('http://localhost:3000/webhook', requestBody, {
    headers: {
      'Content-Type': 'application/json',
      'X-Webhook-Signature': signatureHeader,
    },
  })
  console.log(response.data);
})();

Last updated