Focuses on API authentication, access scopes, and permission management.
Hi,
I was asked to request this here in this forum by Shopify technical support team.
The problem:
Here in this article (https://help.shopify.com/en/api/getting-started/webhooks#creating-an-endpoint-for-webhooks), under the "Verifying Webhooks" heading it says that "To verify that the request came from Shopify, compute the HMAC digest according to the following algorithm and compare it to the value in the X-Shopify-Hmac-SHA256 header". The examples given are PHP and Ruby code; but since we are using Java, we are following sample code given here https://github.com/dworznik/calculate-hmac-sha256/tree/master/src/main/java/net/bzium/shopify . The problem is that the HMAC digest that we compute is never the same as the value that comes as the X-Shopify-Hmac-SHA256 in the header. While computing the HMAC, in the article mentioned above it is said that we should use the shared secret. I also tried with the API Key but that also doesn't match the value that come in the header.
I have already gone through the various Java & HMAC related posts in this forum including this (which, as mentioned in the replies, suggests a hex encryption instead of base64 encryption): https://community.shopify.com/c/Shopify-APIs-SDKs/Webhook-X-Shopify-Hmac-Sha256-calculation-issue-in...
Also the content length (Content-Length) that comes in the header never matches the length of the post request body content.
Any help in this regard would be highly appreciated.
Thanks,
Nabeel
Solved! Go to the solution
This is an accepted solution.
Hi Nabeelhamad,
If you have created your webhook through admin setting under notification, then you have to use the key defined in the notification end of the page after the webhook listing. You can find something like this 'All your webhooks will be signed with 5596ab8fc9524xxxxxxxxxx so you can verify their integrity.'
I hope this helpls,
Thanks,
vinoth
This is an accepted solution.
Hi Nabeelhamad,
If you have created your webhook through admin setting under notification, then you have to use the key defined in the notification end of the page after the webhook listing. You can find something like this 'All your webhooks will be signed with 5596ab8fc9524xxxxxxxxxx so you can verify their integrity.'
I hope this helpls,
Thanks,
vinoth
Hi Vinoth,
Thanks a ton for your input; it worked. So it looks like the documentation given in the article in help.shopify.com is incorrect; it is not the shared secret. Also I noticed that we should use character encoding UTF-8 while reading the data from the HTTP request object.
String jsonString = IOUtils.toString(request.getInputStream(),"UTF-8"); //IOUtils from apache commons String hmac = request.getHeader("X-Shopify-Hmac-Sha256"); String secrete = "webhooks-signature-text"; //get this from shopify admin webhook configuration page boolean validShopifyRequest = Hmac.checkHmac(message, hmac, secret); // used Hmac from the github I mentioned above in my ticket
And everything should work fine!!
Thanks,
Nabeel
Sorry but I got lost with your code, where did the "message" variable came from?
hi,
Sorry - it should be the jsonString; copy/paste error; not able to edit the above one; so adding the corrected one here:
String jsonString = IOUtils.toString(request.getInputStream(),"UTF-8"); //IOUtils from apache commons String hmac = request.getHeader("X-Shopify-Hmac-Sha256"); String secrete = "webhooks-signature-text"; //get this from shopify admin webhook configuration page boolean validShopifyRequest = Hmac.checkHmac(jsonString, hmac, secret); // used Hmac from the github I mentioned above in my ticket
Anyways thanks for pointing it out.
Best Regards,
Nabeel
Hi
I am wondering if this process is any different when we are using Oauth and not webhooks to verify the parameters passed in every request. We are currently using the client id as the secret to verify the parameters and it does not match the hmac . Just wondering if I am doing anything wrong.
This is what we are trying to do : https://shopify.dev/tutorials/authenticate-with-oauth#verification
Here's an example of verifying an application initialization request. Works in Java 11, may work in lower versions, and requires no third party libraries.
public boolean verifySignature(String secretKey, String hmac, String queryString) {
String reducedQueryString = queryString.replaceAll("hmac="+hmac+"&?", "");
if(reducedQueryString.endsWith("&") && reducedQueryString.length() > 1) {
reducedQueryString=reducedQueryString.substring(0, reducedQueryString.length()-1);
}
return verifyHmac(reducedQueryString, hmac, secretKey());
}
public boolean verifyHmac(String message, String hmac, String secretKey) {
try {
Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
hmacSHA256.init(key);
String fx = "%" + hmacSHA256.getMacLength()*2 + "x";
String calculated = String.format(fx, new BigInteger(1, hmacSHA256.doFinal(message.getBytes())));
return hmac.equals(calculated);
} catch (NoSuchAlgorithmException | InvalidKeyException ex) {
LOG.error("Error verifying hmac", ex);
throw new IllegalArgumentException(ex);
}
}
We are creating web-hooks using the Admin API and not the dashboard.
What is the "shared secret" in this case?
We have tried the "client_secret" of the app created but it has failed to work.
This is our java code:
final String hmac = httpServletRequest.getHeader("X-Shopify-Hmac-SHA256");
final String body = IOUtils.toString(httpServletRequest.getInputStream(), StandardCharsets.UTF_8);
SecretKeySpec signingKey = new SecretKeySpec(SECRET.getBytes(), ALGORITHM);
Mac mac = Mac.getInstance(ALGORITHM);
mac.init(signingKey);
String generatedHmacBase64 = Base64.getUrlEncoder().withoutPadding().encodeToString(mac.doFinal(message.getBytes()));
S.O.P(hmac.equals(generatedHmacBase64)); // ALWAYS FALSE
No one is able to answer this question, or no one wants to...
All the validations only work for webhooks created through the dashboard. I have tried in 4 different programming languages to create a means of validating webhooks created through the API and have not been successful using either the api key or the api secret key, nothing works. The sent x-shopify-hmac-sha256 header is ALWAYS different.
I wish someone would finally answer this question!
Hi @garyrgilbert,
The value of the X-Shopify-HMAC-Sha256 HTTP header is expected to be different for each HTTP request, and redirect. You can use your app's shared secret/secret key (not the API key) to validate the HMAC, to verify each request and webhook.
I've recently created a small library to help verify Shopify HMACs here: https://github.com/shopstack-projects/shopstack-security-hmac. I hope the examples and source code are helpful to you.
Questions and feedback welcome! 🙂
@AlanGuerin Thanks, I ended up figuring it out... there was a strange encoding issue which resulted in differing results for some requests..
Sorry, your mini library gives a good solution, but after I introduced it through maven, I couldn't start the project.
java: Unable to access dev.shopstack.security.hmac.HmacVerifier
Wrong class file: /Users/edy/.m2/repository/dev/shopstack/security/shopstack-security-hmac/1.1.0.RELEASE/shopstack-security-hmac-1.1.0.RELEASE.jar!/dev/shopstack/security/hmac/HmacVerifier.class
The class file has the wrong version 55.0, which should be 52.0
Delete the file or make sure it is in the correct path subdirectory.
The library is compiled using Java 11 (version 55.0), which I've noted in the README. You are using Java 8 (version 52.0). You will need to use Java 11 or above to use this library.
Had the same issue. Using request.rawBody instead of request.body helped:
import Router from "koa-router";
import koaBodyParser from "koa-bodyparser";
import crypto from "crypto";
...
koaServer.use(koaBodyParser());
...
koaRouter.post(
"/webhooks/<yourwebhook>",
verifyShopifyWebhooks,
async (ctx) => {
try {
ctx.res.statusCode = 200;
} catch (error) {
console.log(`Failed to process webhook: ${error}`);
}
}
);
...
async function verifyShopifyWebhooks(ctx, next) {
const generateHash = crypto
.createHmac("sha256", process.env.SHOPIFY_WEBHOOKS_KEY) // that's not your Shopify API secret key, but the key under Webhooks section in your admin panel (<yourstore>.myshopify.com/admin/settings/notifications) where it says "All your webhooks will be signed with [SHOPIFY_WEBHOOKS_KEY] so you can verify their integrity
.update(ctx.request.rawBody, "utf-8")
.digest("base64");
if (generateHash !== shopifyHmac) {
ctx.throw(401, "Couldn't verify Shopify webhook HMAC");
} else {
console.log("Successfully verified Shopify webhook HMAC");
}
await next();
}