2

Im trying to verify a webhook signature, I have this PHP code from the documentation of api2cart but I need it in Javascript.I tried however I couldn't match the signature and the HMAC generated value, more details here

steps to follow:

  1. Gather all headers starting with “X-Webhook-” // I received it as x-webhook- in my header, don't know if it affects the encryption

  2. Remove “X-Webhook-Signature” from the array.

  3. Sort the headers alphabetically by the key

  4. Encode the headers into JSON.

  5. Concatenate the strings, JSON headers string should come first

  6. Hash the resulting string with HMAC SHA256 function with outputs raw binary data set true (raw_output = true)

Use your store_key as a secret key to generate a binary signature

  1. Encode the string in Base64

$headersForJson = [
'X-Webhook-Error-Code' => '0',
'X-Webhook-Action' => 'update',

'X-Webhook-Timestamp' => '1516291592',

'X-Webhook-Entity' => 'product',

'X-Webhook-Store-Id' => '1',

'X-Webhook-Signature' => 'SGVsbG8gd2l0aCBBUEkyQ2FydA==',

];

$signatureFromRequest = $headersForJson['X-Webhook-Signature'];

unset($headersForJson['X-Webhook-Signature']);

ksort($headersForJson);

$headers = json_encode($headersForJson);

$data = $headers . $params['raw_body'];

$generatedSignature = base64_encode(hash_hmac('sha256', $data, $storeKey, true));

 if (hash_equals($signatureFromRequest, $generatedSignature)) {
   return true;
 }

Here is what I did:

const signature = headers['x-webhook-signature'];
delete headers['x-webhook-signature'];
    // the header contained other keys I had to get keys starting with x-webhooks
    let xkeys = Object.keys(headers).filter(key => key.includes('x-webhook-')).sort();
    let xheaders = JSON.stringify(xkeys.reduce((res, key) => Object.assign(res, { [key]: headers[key] }), {}));
    let data = xheaders + rawBody


const generatedHash = createHmac('SHA256', "SecretKey")
            .update(data, 'utf-8')
            .digest('base64');


return generatedHash === signature

what am I missing here ?

1
  • Hi, have you solved it to this day? Commented Oct 26, 2021 at 14:22

2 Answers 2

0

You need to convert header array keys from, for example, x-webhook-entity to X-Webhook-Entity

for example:

   let xheaders = JSON.stringify(
      xkeys.reduce((res, key) => Object.assign(
        res,
        { [key.split('-').map(s => s[0].toUpperCase() + s.slice(1)).join('-')]: headers[key] }),//format header key from x-webhook-entity to X-Webhook-Entity
        {}
      )
    );

Full code with Pipedream:

const crypto = require('crypto');
const storeKey = '5d780e682fbdbd4d04411be86ccd4b30';
const signature = event.headers['x-webhook-signature'];

const headers = event.headers;
delete headers['x-webhook-signature'];

// the header contained other keys I had to get keys starting with x-webhooks
let xkeys = Object.keys(headers).filter(key => key.includes('x-webhook-')).sort();
let xheaders = JSON.stringify(
  xkeys.reduce((res, key) => Object.assign(
    res,
    { [key.split('-').map(s => s[0].toUpperCase() + s.slice(1)).join('-')]: headers[key] }),//format header key from x-webhook-entity to X-Webhook-Entity
    {}
  )
);

const data = xheaders + JSON.stringify(event.body);
const generatedHash = crypto.createHmac('SHA256', storeKey)
            .update(data, 'utf-8')
            .digest('base64');

console.log(signature);
console.log(generatedHash);

let status = 403;

if (signature === generatedHash) {
  status = 200;
}

const response = await $respond({
  status: status,
  immediate: true,
  headers: {},
  body: {"signature":signature, "generatedHash":generatedHash} 
})

return JSON.parse(response.config.data).response
Sign up to request clarification or add additional context in comments.

Comments

0

I had the same issue and I solved it with:

  1. headers must be in capital letters
  2. body must be JSON.stringify (req.body) and req.body must be JSON object, not text (not raw_body)

This works for me:

const validateRequest = (headers, body, storeKey) => {
  const webhookHeaders = {
    "X-Webhook-Action": headers["x-webhook-action"],
    "X-Webhook-Entity": headers["x-webhook-entity"],
    "X-Webhook-Error-Code": headers["x-webhook-error-code"],
    "X-Webhook-Store-Id": headers["x-webhook-store-id"],
    "X-Webhook-Timestamp": headers["x-webhook-timestamp"]
  };

  const signatureFromRequest = headers["x-webhook-signature"];
  const data = JSON.stringify(webhookHeaders) + JSON.stringify(body);
  const generatedSignature = crypto
    .createHmac("sha256", storeKey)
    .update(data)
    .digest("base64");

  return !!crypto.timingSafeEqual(
    Buffer.from(signatureFromRequest),
    Buffer.from(generatedSignature)
  );
};

you can call it with this:

const headers = {
  "x-webhook-entity": "product",
  "x-webhook-action": "update",
  "x-webhook-store-id": "0",
  "x-webhook-error-code": "0",
  "x-webhook-timestamp": "1635502511",
  "x-webhook-signature": "Ua1dtsDBi+37fEGr3mTHN7laZqLQpl+tEK02RDAg++0="
};
const body = { id: "28" };
const storeKey = "ed58a22dfecb405a50ea3ea56979360d";
const isValid = validateRequest(headers, body, storeKey);

console.log(isValid ? "success" : "failed"); // success

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.