I am setting up Shopify webhooks for my app and I want to verify the webhooks. I am creating the webhooks using this API. I am verifying the webhooks in Node Js and this is the code that I am using to do it.
const verifyWebhook = async (req, res, next) => {
const hmac = req.get("X-Shopify-Hmac-Sha256");
const body = await getRawBody(req);
// Create hash based on body and key
const generatedHash = crypto
.createHmac("sha256", secretKey)
.update(body)
.digest("base64");
// Compare our hash to Shopify's hash
if (generatedHash !== hmac) {
return res.sendStatus(401);
}
req.body = JSON.parse(body);
next();
};
However, I am not so sure about the secret that I should use. I tried using the API secret available on the credentials tab for an app but it doesn’t seem to work.
However, when I tried creating the webhooks from the store settings and used the secret available there then it works fine but I am not sure about the secret I should be using in case of webhooks created using the API. I have read the documentation and it mentions some shared secret but I am not able to figure out what it is.
Any help is highly appreciated and thanks in advance.
1 Like
I’m also having a similar issue. Here is my code.
// pages/api/shopifyWebhook.js
import crypto from 'crypto';
import clientPromise from '../../utils/mongodb'; // Import MongoDB utility
export const config = {
api: {
bodyParser: false, // Disables Next.js body parsing to access the raw body
},
};
export default async function handler(req, res) {
const secret = 'SECRET_HERE'; //I've tried all the secrets I have access to in shopify apps, and not sure which one to use.
if (req.method === 'POST') {
// Extract the Shopify HMAC signature from the request headers
const hmacHeader = req.headers['x-shopify-hmac-sha256'];
// Get the raw body of the request
const body = await getRawBody(req);
// Compute an HMAC with SHA256 hash of the body using the secret
const hash = crypto
.createHmac('sha256', secret)
.update(body, 'utf8')
.digest('base64');
if (crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(hmacHeader))) {
// Parse the verified webhook payload
const webhook = JSON.parse(body);
console.log('Webhook verified and processed:', webhook);
res.status(200).json({ message: 'Webhook processed and data saved' });
} catch (error) {
console.error('Database operation failed', error);
res.status(500).json({ success: false, message: 'Internal server error' });
}
} else {
res.status(200).json({ message: 'Webhook verified but no action taken' });
}
} else {
return res.status(403).json({ error: 'Verification failed' });
}
} else {
// Handle non-POST requests or other logic
res.setHeader('Allow', ['POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
async function getRawBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (chunk) => { body += chunk.toString(); });
req.on('end', () => { resolve(body); });
req.on('error', (err) => { reject(err); });
});
}