Topics covering webhook creation & management, event handling, Pub/Sub, and Eventbridge, in Shopify apps.
Hi everyone,
I used the Shopify with Shopify CLI 3 to create a NodeJS Shopify app using the command 'npm init @Shopify/app@latest'. This app is turned into a sales channel app and installed on my development store. Then I made a listener for webhooks as described in the documentation. This is my code currently:
app.post("/static/webhooks", express.text({type: '*/*'}), async (_req, res) => { try { await shopify.api.webhooks.process({ rawBody: _req.body, // is a string rawRequest: _req, rawResponse: res, }); } catch (error) { console.log(error.message); } });
There are 2 problems with this code.
1. The 'express.text({type: '*/*'})' middleware should turn the _req.body into a string, but it does not do that. The body is just an object. This results in the error 'No body was received when processing webhook'. I bypass that problem by replacing '_req.body' with 'JSON.stringify(_req.body)'.
2. When I replace the '_req.body' with 'JSON.stringify(_req.body)', I get the error 'Could not validate request HMAC'. I tried recreating the HMAC by using the 'crypto' module using both my secret API key from Shopify Partners and the key that is shown at the bottom of the Shop admin at the 'webhooks' section, but both do not produce the same HMAC as in the header 'x-shopify-hmac-sha256'.
What is going wrong here? I have a feeling I might be using the wrong API version or something, since I have to use 'shopify.api.webhooks.process' instead of 'shopify.webhooks.process', but I don't know if that is the problem or where I could find documentation on this updated API. Any help is greatly appreciated.
Thanks!
The documentation was a bit unhelpful on my end as well but try using this instead. It worked for me:
use this middleware instead:
express.raw({ type: 'application/json' })
The actual function to get the header and check is as follows:
const hmacHeader = req.headers['x-shopify-hmac-sha256'];
const data = req.body as Buffer;
console.log({hmacHeader, data});
if(!hmacHeader || !data){
res.status(400).send('Bad Request');
return;
};
const verified = verifyShopifyWebhook(data, hmacHeader as string);
if (!verified) {
res.status(401).send('Unauthorized');
return;
};
// Rest of your route code here
My verify webhook function is using "crypto"
import * as crypto from 'crypto';
const CLIENT_SECRET = process.env.SHOPIFY_CLIENT_SECRET as string;
export function verifyShopifyWebhook(data: Buffer, hmacHeader: string): boolean {
const hash = crypto
.createHmac('sha256', CLIENT_SECRET)
.update(data.toString(), 'utf8')
.digest('base64');
// Ensure hmacHeader and hash are of the same length
if (hmacHeader.length !== hash.length) {
return false;
}
return crypto.timingSafeEqual(Buffer.from(hmacHeader), Buffer.from(hash));
}
I am using typescript node v16 and Shopify 3.0 CLI.
Please kindly like and approve the solution! I am trying to grow my status to help better assist Merchants!