Nodejs - Expressjs - Verify shopify webhook - node.js

I am trying to verify the hmac code sent from a shopify webhook on a dev environment. However shopify will not send a post request for a webhook to a non live endpoint, so I am using requestbin to capture the request and then use postman to send it to my local webserver.
From shopify documentation, I seem to be doing everything right and have also tried applying the method used in node-shopify-auth verifyWebhookHMAC function. But none of this has worked so far.
The codes are never a match.
What am I doing wrong here?
My code to verify the webhook:
function verifyWebHook(req, res, next) {
var message = JSON.stringify(req.body);
//Shopify seems to be escaping forward slashes when the build the HMAC
// so we need to do the same otherwise it will fail validation
// Shopify also seems to replace '&' with \u0026 ...
//message = message.replace('/', '\\/');
message = message.split('/').join('\\/');
message = message.split('&').join('\\u0026');
var signature = crypto.createHmac('sha256', shopifyConfig.secret).update(message).digest('base64');
var reqHeaderHmac = req.headers['x-shopify-hmac-sha256'];
var truthCondition = signature === reqHeaderHmac;
winston.info('sha256 signature: ' + signature);
winston.info('x-shopify-hmac-sha256 from header: ' + reqHeaderHmac);
winston.info(req.body);
if (truthCondition) {
winston.info('webhook verified');
req.body = JSON.parse(req.body.toString());
res.sendStatus(200);
res.end();
next();
} else {
winston.info('Failed to verify web-hook');
res.writeHead(401);
res.end('Unverified webhook');
}
}
My route which receives the request:
router.post('/update-product', useBodyParserJson, verifyWebHook, function (req, res) {
var shopName = req.headers['x-shopify-shop-domain'].slice(0, -14);
var itemId = req.headers['x-shopify-product-id'];
winston.info('Shopname from webhook is: ' + shopName + ' For item: ' + itemId);
});

I do it a little differently -- Not sure where I saw the recommendation but I do the verify in the body parser. IIRC one reason being that I get access to the raw body before any other handlers are likely to have touched it:
app.use( bodyParser.json({verify: function(req, res, buf, encoding) {
var shopHMAC = req.get('x-shopify-hmac-sha256');
if(!shopHMAC) return;
if(req.get('x-kotn-webhook-verified')) throw "Unexpected webhook verified header";
var sharedSecret = process.env.API_SECRET;
var digest = crypto.createHmac('SHA256', sharedSecret).update(buf).digest('base64');
if(digest == req.get('x-shopify-hmac-sha256')){
req.headers['x-kotn-webhook-verified']= '200';
}
}}));
and then any web hooks just deal with the verified header:
if('200' != req.get('x-kotn-webhook-verified')){
console.log('invalid signature for uninstall');
res.status(204).send();
return;
}
var shop = req.get('x-shopify-shop-domain');
if(!shop){
console.log('missing shop header for uninstall');
res.status(400).send('missing shop');
return;
}

Short Answer
The body parser in express does not handle BigInt well, and things like order number which are passed as integer get corrupted. Apart from that certain values are edited such as URLs are originally sent as "https://...", which OP also found out from the other code.
To solve this, do not parse the data using body parser and instead get it as raw string, later on you can parse it with json-bigint to ensure none of it has been corrupted.
Long Answer
Although the answer by #bknights works perfectly fine, it's important to find out why this was happening in the first place.
For a webhook I made on the "order_created" event from Shopify I found out that the id of the request being passed to the body was different than what I was sending from my test data, this turned out to be an issue with body-parser in express which did not play nice with big integers.
Ultimately I was deploying something to Google cloud functions and the req already had raw body which I could use, but in my test environment in Node I implemented the following as a separate body parser as using the same body parser twice overwrote the raw body with JSON
var rawBodySaver = function (req, res, buf, encoding) {
if (buf && buf.length) {
req.rawBody = buf.toString(encoding || 'utf8');
}
}
app.use(bodyParser.json({verify: rawBodySaver, extended: true}));
Based on this answer
I later on parse the rawBody using json-bigint for use in code elsewhere as otherwise some of the numbers were corrupted.

// Change the way body-parser is used
const bodyParser = require('body-parser');
var rawBodySaver = function (req, res, buf, encoding) {
if (buf && buf.length) {
req.rawBody = buf.toString(encoding || 'utf8');
}
}
app.use(bodyParser.json({ verify: rawBodySaver, extended: true }));
// Now we can access raw-body any where in out application as follows
// request.rawBody in routes;
// verify webhook middleware
const verifyWebhook = function (req, res, next) {
console.log('Hey!!! we got a webhook to verify!');
const hmac_header = req.get('X-Shopify-Hmac-Sha256');
const body = req.rawBody;
const calculated_hmac = crypto.createHmac('SHA256', secretKey)
.update(body,'utf8', 'hex')
.digest('base64');
console.log('calculated_hmac', calculated_hmac);
console.log('hmac_header', hmac_header);
if (calculated_hmac == hmac_header) {
console.log('Phew, it came from Shopify!');
res.status(200).send('ok');
next();
}else {
console.log('Danger! Not from Shopify!')
res.status(403).send('invalid');
}
}

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();
}

Related

Why stripe webhook is returning 400 error and how to fix it?

I am trying to get the webhook working for stripe but constantly getting the following error: Webhook Error: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing. I have followed their documentation and read through other SO users who had the same issue but still no success. Here is what I have in my code:
index.js
app.use('/webhooks/stripe', bodyParser.raw({type: "application/json"}), stripeWebhookRouter);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json({
verify: function(req,res,buf) {
var url = req.originalUrl;
if (url.startsWith('/stripe-webhooks')) {
req.rawBody = buf.toString()
}
}
}));
And here is my stripe webhook file that listen on certain endpoint
stripeWebhook.js
let stripeSecretKey;
if(process.env.NODE_ENV === 'production'){
stripeSecretKey = process.env.STRIPE_WEBHOOK_SECRET_PRODUCTION;
}else{
stripeSecretKey = process.env.STRIPE_SK_TEST;
}
const stripe = require('stripe')(stripeSecretKey);
const stripeWebhookRouter = Router();
stripeWebhookRouter.post('/stripeWebhook', async (request, response) => {
console.log("Stripe webhook working")
const payload = request.body;
const sig = request.headers['stripe-signature'];
let event;
try {
event = await stripe.webhooks.constructEvent(payload, sig, stripeSecretKey);
console.log("Stripe event constructed ", event);
} catch (err) {
console.log(`❌ Error message: ${err.message}`);
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// I have more code logic here, but it is irrelevant to this question
}
Am I setting up the bodyParser wrong for the Stripe router?
Okay fellow developers, I think I nailed the solution and if anyone faces similar issue please take look at my solution. I also strongly suggest that you have separate file for your router collection, because this will ensure that your index.js file is not clogged with bunch of routers.
So the issue appeared to be not being able to pass down raw incoming request to the stripe webhook endpoint. Here is my code change:
index.js
app.use('/webhooks/stripe', express.raw({type: "*/*"}), stripeWebhookRouter);
app.use((req, res, next) => {
if(req.originalUrl.startsWith('/stripe-webhooks')){
next();
}else{
express.json()(req, res, next);
}
})
and here us my stripeWebhook.js
const payload = request.body;
const sig = request.headers['stripe-signature'];
let event;
try {
event = await stripe.webhooks.constructEvent(payload, sig, stripeWebhookSecretKey);
console.log("✅ Stripe event constructed ", event);
} catch (err) {
console.log(`❌ Stripe event construction failed, error message: ${err.message}`);
return response.status(400).send(`Webhook Error: ${err.message}`);
}
Pay attention to how you parse incoming requests to your stripe webhook endpoint. Cheers!

HTTP Post undefined in node

I'm trying to create an app in Node.JS that receives POST requests. At the moment, I have them tunneled through ngrok, and I can get the headers without a problem, but I can't get the request body. Here's my code:
const http = require("http");
const crypto = require("crypto");
const qs = require("qs");
const bodyParser = require("body-parser");
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
http.createServer(function(request) {
let x_slack_signature = request.headers["x-slack-signature"]; // Get the Slack signature
let raw_body = request.body; // Get the request body
let x_slack_timestamp = request.headers["x-slack-request-timestamp"]; // Get the timestamp of the request
(...)
}).listen(3000);
But when I make a console log with the variables I defined, raw_body returns either empty or undefined.
I have tried to qs.stringify since I was reading that seemed to work. Also with body parser; I get this error body-parser deprecated undefined extended: provide extended option. If I write the extended option then it outputs this:
function urlencodedParser (req, res, next) {
if (req._body) {
debug('body already parsed')
next()
return
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
debug('skip empty body')
next()
return
}
debug('content-type %j', req.headers['content-type'])
// determine if request should be parsed
if (!shouldParse(req)) {
debug('skip parsing')
next()
return
}
// assert charset
var charset = getCharset(req) || 'utf-8'
if (charset !== 'utf-8') {
debug('invalid charset')
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
charset: charset,
type: 'charset.unsupported'
}))
return
}
// read
read(req, res, next, parse, debug, {
debug: debug,
encoding: charset,
inflate: inflate,
limit: limit,
verify: verify
})
}
What am I doing wrong?
I am trying to get the full body first, then to divide it into something like an array of some sorts
Thanks in advance

Stripe webhook not working in firebase function

I'm setting up a stripe webhook to fulfill my order with stripe after a payment while hosting it with firebase functions.
When developing on a localhost everything works fine until I publish it in a firebase function where it always sends me a [400] error.
Creating the payment intent works fine in the same firebase function but that one doesn't.
The error message I get is : No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing
Code of my express backend :
app.post(
"/stripe/webhook",
bodyParser.raw({ type: "application/json" }),
async (request, response) => {
const payload = request.body;
const sig = request.headers["stripe-signature"];
let event;
try {
event = stripe.webhooks.constructEvent(payload, sig, endpointSecret);
} catch (err) {
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the checkout.session.completed event
if (event.type === "checkout.session.completed") {
const session = event.data.object;
oldSession = await stripe.checkout.sessions.listLineItems(
session.id,
function (err, lineItems) {
fulfillOrder(session, lineItems);
}
);
}
response.status(200);
}
);
Anyone has an idea on how to make it work ? Thanks
I had the same problem but managed to find a solution that worked for me. First request the rawBody in a middleware using verify:
app.use(bodyParser.json({
verify: (req: any, res, buf, encoding) => {
// get rawBody
req.rawBody = buf.toString()
}
}))
Then parse the buffer as payload:
const payload = req.rawBody.toString()
request.body is a pre-parsed object of the request body and should not be used for verification. Instead you likely want request.rawBody which is a Buffer of the raw request body unparsed and unmodified. Try using that instead (you may need to convert it to a string) in your constructEvent call.

Node and Twilio integration

I have written a programmable SMS feature using Twilio in nodejs. I have a message that has options to select and when user sends back any response I want to send an automated response using twilio.
I have completed all except after processing the response from user my automated response is not being delivered to user.
I keep getting above thing from my twilio dashboard.
Here is my response handler code..
app.post('/ui/sms',function(req, res) {
//req.headers['Content-type'] = 'text/xml';
//req.headers['Accept'] = 'text/xml';
try {
console.log('Processing Response', req.headers);
const MessagingResponse = require('twilio').twiml.MessagingResponse;
const twiml = new MessagingResponse();
const fromTwilio = isFromTwilio(req);
console.log('isFromTwilio: ', fromTwilio);
if (fromTwilio) {
let msg = req.body.Body||'';
if (msg.indexOf('1')>-1) {
twiml.message('Thanks for confirming your appointment.');
} else if (msg.indexOf('2')>-1) {
twiml.message('Please call 408-xxx-xxxx to reschedule.');
} else if (msg.indexOf('3')>-1) {
twiml.message('We will call you to follow up.');
} else {
twiml.message(
'Unknown option, please call 408-xxx-xxxx to talk with us.'
);
}
res.writeHead(200, { 'Content-Type': 'text/xml' });
res.end(twiml.toString());
}
else {
// we don't expect these
res.status(500).json({ error: 'Cannot process your request.' });
}
/*processSMSResponse(req, function(response) {
res.json(response);
});*/
} catch(e) {
res.json(e);
}
});
function isFromTwilio(req) {
console.log('REQ HEADER:::::::::\n', req);
// Get twilio-node from twilio.com/docs/libraries/node
const client = require('twilio');
// Your Auth Token from twilio.com/console
const authToken = 'xxxxxxxxxxxxxxxxx';
// The Twilio request URL
//const url = 'https://mycompany.com/myapp.php?foo=1&bar=2';
const url = 'https://xxxx.com/ui/sms';
var reqUrl = 'https://xxxx.com/ui/sms'
// The post variables in Twilio's request
//const params = {
//CallSid: 'CA1234567890ABCDE',
//Caller: '+14158675310',
//Digits: '1234',
//From: '+14158675310',
//To: '+18005551212',
//};
const params = req.body;
console.log('post params: ', params);
// The X-Twilio-Signature header attached to the request
try{
Object.keys(params).sort().forEach(function(key) {
reqUrl = reqUrl + key + params[key];
});
var twilioSignature = crypto.createHmac('sha1', authToken).update(Buffer.from(reqUrl, 'utf-8')).digest('base64');
//const twilioSignature = req.header('HTTP_X_TWILIO_SIGNATURE');
console.log('twilioSignature: ', twilioSignature);
} catch(e){
console.log(e);
}
return client.validateRequest(
authToken,
twilioSignature,
url,
params
);
}
I have explicitly tried setting headers but no use. I'm clue less on what twilio expects from me or how to modify headers.
{
"status": "Error",
"error": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response><Message>Please call 408-xxx-xxxx to reschedule.</Message></Response>"
}
I see this as body in Twilio console and it has the response that I need but could not send as a message..
Twilio Developer Evangelist here.
From looking at your code and trying it myself I can't see anything generally wrong with it. There are two potential failing points here though:
1) I can't see your isFromTwilio function. If that one fails it might cause an error and then return JSON instead of XML on your error handler. I don't know why it would reply with the TwiML in that JSON though.
2) The other behavior I could reproduce (except that the TwiML is not being sent in the response body) is when I don't include body-parser in the middleware chain. This will cause req.body to be undefined and therefore req.body.Body will throw an error that is then being caught and JSON is being returned.
Do you have body-parser included and properly included as a middleware? You can either do it this way:
const { urlencoded } = require('body-parser');
app.use(urlencoded({ extended: false });
or if you only want to use it for this one endpoint you can add urlencoded({ extended: false }) as an argument before your request handler:
app.post('/ui/sms', urlencoded({ extended: false }), function(req, res) {
I hope this helps.
Cheers,
Dominik

Node/Express - How to send get request to check a status code

I am trying to make a URL shortener. I need to take a given URL as a parameter and send a request to that URL just to get the status code. If status = 200, I know I've got a functioning URL, and I'll go ahead and add it to the DB and shorten it.
Problem is, when I make that request, the connection times out.
const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')
const nofavicon = require('express-no-favicons')
const Shortener = require('./shortener')
const app = express()
app.disable('x-powered-by')
app.use(cors())
app.use(nofavicon())
app.use(express.static(__dirname + '/static'))
mongoose.connect(
process.env.MONGODB_URI || 'mongodb://heroku_x7hcc5zd:39c8i70697o7qrpjn4rd6kslch#ds123371.mlab.com:23371/heroku_x7hcc5zd'
)
app.get('/url/:urlParam(*)', (request, response) => {
let urlParam = request.params.urlParam
let urlRegEx = /[A-Za-z]+[://]+[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&;?#/.=]+/g
if (urlRegEx.test(urlParam)) {
let shortRandomNum = Math.floor(Math.random() * 10000).toString()
// Shortener here refers to a mongoose Schema in external file
let lmao = new Shortener({
url: urlParam,
urlmao: 'localhost:8080/lol/' + shortRandomNum,
})
// Request header from passed URL to verify legitimacy
// Check statusCode and end request.
app.head(urlParam, (req, res) => {
let end = res.end
// Override standard res.end function with custom function
res.end = () => {
if (res.statusCode == 200) {
lmao.save((error) => {
if (error) {
response.send('Unable to write to collection')
}
})
console.log('pass')
response.json({lmao})
}
}
res.end = end
res.end()
})
} else {
// If passed URL does not satisfy regEx, return error message.
urlParam = 'unfunny url. http(s):// prefix required. check url and retry.'
console.log('invalid url')
response.json({
url: urlParam,
})
}
})
app.listen(process.env.PORT || 8080, () => {
console.log('live connection')
})
Most baffingly, the code shown here worked on Friday. Tested it last night, no dice. Any insight would be greatly, greatly appreciated.
app.head(urlParam, [Function]) doesn't make a request to the url, it defines a new route on your application so that it responds to HEADrequests on that url.
To check if the URL is alive you need to use another package to make requests. One of my favourites is Request. To use it simply replace app.head with request and add the require('request') to the top of your file.

Resources