Hello Shopify Developers,
I’m encountering an issue with my Express.js application where, despite specifying a 401 Unauthorized
status code for invalid HMAC signatures in my webhook handler, the response returns a 200 OK
status. This behavior is causing automated webhook checks to fail with the following error:
Error: Expected HTTP 401 (Unauthorized)
Received HTTP 200 from https://eoog0edzqpkgtmg.m.pipedream.net
Your app’s HTTPS webhook endpoints must validate the HMAC digest of each request and return an HTTP 401 response when rejecting a request with an invalid digest.
Code Snippet:
const express = require('express');
const app = express();
const crypto = require('crypto');
const SHOPIFY_API_SECRET_KEY = 'your_secret_key';
app.use(express.json());
const log = (message) => {
console.log(message);
};
const hmacTest = (secret, body) => {
const expectedHmac = crypto
.createHmac('sha256', secret)
.update(body)
.digest('base64');
if (expectedHmac !== crypto.createHmac('sha256', secret).update(body).digest('base64')) {
console.error('HMAC signature test failed!');
console.error(`Expected HMAC: ${expectedHmac}`);
console.error(`Actual HMAC: ${crypto.createHmac('sha256', secret).update(body).digest('base64')}`);
return { error: true, code: 503, message: 'HMAC signature test failed' };
}
return { error: false };
};
app.post('/webhook', (req, res) => {
log('Received webhook request:', req.body);
const hmacHeader = req.headers['x-shopify-hmac-sha256'];
log('HMAC header:', hmacHeader);
const secret = SHOPIFY_API_SECRET_KEY;
const body = JSON.stringify(req.body);
const hmacResult = hmacTest(secret, body);
if (hmacResult.error) {
res.status(hmacResult.code).send(hmacResult.message);
return;
}
const generatedHmac = crypto
.createHmac('sha256', secret)
.update(body)
.digest('base64');
if (generatedHmac === hmacHeader) {
log('HMAC valid! Proceeding with webhook handling...');
res.status(200).send('Webhook received successfully.');
} else {
log('HMAC mismatch! Expected:', generatedHmac, 'but got:', hmacHeader);
res.status(401).send('Invalid HMAC signature');
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Issue Details:
Despite the conditional check for HMAC validation and the explicit res.status(401).send('Invalid HMAC signature');
line, the response sent to Shopify is a 200 OK
. This discrepancy leads to failed automated webhook checks, as Shopify expects a 401 Unauthorized
status for invalid HMAC signatures.
Troubleshooting Steps Taken:
- Middleware Order: Ensured that the
express.json()
middleware is correctly placed to parse JSON bodies before the webhook handler. - HMAC Calculation: Verified that the HMAC is computed using the raw request body string.
- Logging: Added logs to confirm that the code execution reaches the point where
res.status(401).send('Invalid HMAC signature');
is called.
Request for Assistance:
I’m seeking insights into why the response status defaults to 200
despite specifying 401
in the code. Are there aspects of Express.js’s response handling or middleware behavior that might cause this? Any guidance or suggestions to ensure the correct status code is returned would be greatly appreciated.
Thank you in advance!