Solved

Node and expressjs - unable to subscribe webhook

marsh4
Shopify Partner
3 1 1

Hi There, 

 

New to node and express. I am trying to subscribe a webhook using API with our application upon installing the app. I have followed the documentation on creating an app with Node and express (https://help.shopify.com/en/api/tutorials/build-a-shopify-app-with-node-and-express) and that all works fine. When i try to change the code to POST and subscribe a webhook (instead of the GET shop data in the instructions), I then run into trouble. I have posted the error below and the full code. Hoping someone can help me out here as I feel I am missing something simple.

 
Error I receive:

Unhandled rejection RangeError [ERR_HTTP_INVALID_STATUS_CODE]: Invalid status code: undefined
at ServerResponse.writeHead (_http_server.js:209:11)
at ServerResponse._implicitHeader (_http_server.js:200:8)
at ServerResponse.end (_http_outgoing.js:705:10)
at ServerResponse.send (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\node_modules\express\lib\response.js:221:10)
at request.post.then.catch (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\index.js:109:32)
at tryCatcher (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\node_modules\bluebird\js\release\util.js:16:23)
at Promise._settlePromiseFromHandler (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\node_modules\bluebird\js\release\promise.js:517:31)
at Promise._settlePromise (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\node_modules\bluebird\js\release\promise.js:574:18)
at Promise._settlePromise0 (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\node_modules\bluebird\js\release\promise.js:619:10)
at Promise._settlePromises (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\node_modules\bluebird\js\release\promise.js:695:18)
at _drainQueueStep (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\node_modules\bluebird\js\release\async.js:138:12)
at _drainQueue (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\node_modules\bluebird\js\release\async.js:131:9)
at Async._drainQueues (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\node_modules\bluebird\js\release\async.js:147:5)
at Immediate.Async.drainQueues [as _onImmediate] (C:\Users\Scott's PC\Desktop\Tapn\tapn-app-installer\node_modules\bluebird\js\release\async.js:17:14)
at runCallback (timers.js:705:18)
at tryOnImmediate (timers.js:676:5)
at processImmediate (timers.js:658:5)

 

 

 

Full code:

 


const dotenv = require('dotenv').config();
const express = require('express');
const app = express();
const crypto = require('crypto');
const cookie = require('cookie');
const nonce = require('nonce')();
const querystring = require('querystring');
const request = require('request-promise');

const apiKey = process.env.SHOPIFY_API_KEY;
const apiSecret = process.env.SHOPIFY_API_SECRET;
const scopes = 'read_orders';
const forwardingAddress = "https://a4cd4527.ngrok.io"// Replace this with your HTTPS Forwarding address


// Redirects user to app authorisation prompt
app.get('/tapn/install', (reqres=> {
    const shop = req.query.shop;
    if (shop) {
      const state = nonce();
      const redirectUri = forwardingAddress + '/tapn/auth';
      const installUrl = 'https://' + shop +
        '/admin/oauth/authorize?client_id=' + apiKey +
        '&scope=' + scopes +
        '&state=' + state +
        '&redirect_uri=' + redirectUri;
  
      res.cookie('state'state);
      res.redirect(installUrl);
    } else {
      return res.status(400).send('Missing shop parameter. Please add ?shop=your-development-shop.myshopify.com to your request');
    }
  });

 
// After a user has acepted
app.get('/tapn/auth', (reqres=> {
    const { shophmaccodestate } = req.query;
    const stateCookie = cookie.parse(req.headers.cookie).state;
  
    if (state !== stateCookie) {
      return res.status(403).send('Request origin cannot be verified');
    }

  
    if (shop && hmac && code) {
        // Validate request is from Shopfiy
        const map = Object.assign({}, req.query);
        delete map['signature'];
        delete map['hmac'];
        const message = querystring.stringify(map);
        const providedHmac = Buffer.from(hmac'utf-8');
        const generatedHash = Buffer.from(
          crypto
            .createHmac('sha256'apiSecret)
            .update(message)
            .digest('hex'),
            'utf-8'
          );
        let hashEquals = false;

        // timingSafeEqual will prevent any timing attacks. Arguments must be buffers
        try {
          hashEquals = crypto.timingSafeEqual(generatedHashprovidedHmac)
        
        // timingSafeEqual will return an error if the input buffers are not the same length.
        } catch (e) {
          hashEquals = false;
        };
        
        if (!hashEquals) {
          return res.status(400).send('HMAC validation failed');
        }
        

// Exchanging permenant access token
const accessTokenRequestUrl = 'https://' + shop + '/admin/oauth/access_token';
const accessTokenPayload = {
client_id: apiKey,
client_secret: apiSecret,
code,
};

request.post(accessTokenRequestUrl, { json: accessTokenPayload })
.then((accessTokenResponse=> {
const accessToken = accessTokenResponse.access_token;


//Creating Webhook

const shopRequestUrl = 'https://' + shop + '/admin/api/2019-07/webhooks.json';
const shopRequestHeaders = {
  'X-Shopify-Access-Token': accessToken,
};
const shopRequestBody = {
    "webhook": {
      "topic": "orders/create",
      "address": "https://entaabyzocf5e.x.pipedream.net/",
      "format": "json"
  }
};

request.post(shopRequestUrl, { headers: shopRequestHeaders }, { json: shopRequestBody })
.then((shopResponse=> {
  res.status(200).end(shopResponse);
})
.catch((error=> {
  res.status(error.statusCode).send(error.error.error_description);
});
})

.catch((error=> {
res.status(error.statusCode).send(error.error.error_description);
});

else {
  res.status(400).send('Required parameters missing');
}

  });

// App lisetning on port 3000
app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
});


 

 
Accepted Solution (1)
marsh4
Shopify Partner
3 1 1

This is an accepted solution.

Hi Evita, 

 

I tried loading the dependencies and adjusting the code but was still unable to get it working. I managed to use axios and followed the instructions in their documentation and got this working (https://www.npmjs.com/package/axios).

 

Thanks for you help.

View solution in original post

Replies 2 (2)

OTM
Shopify Expert
696 170 252

Hi, @marsh4 ,

This is Evita from On The Map.

 

Make sure you install this dependencie - @shopify/koa-shopify-webhooks

Read more here - https://developers.shopify.com/tutorials/build-a-shopify-app-with-node-and-react/listen-for-store-ev...

 

Best,
Evita

On The Map Marketing | Developing custom Shopify Sites & Apps is our thing

- Install our latest app Accessibly - Makes your store accessible for everyone, helps to avoid fines
- Inc 5000 | Shopify Parners | 20+ stores launched | 300+ active clients
- Need help with your Shopify store? Reach out to us!
marsh4
Shopify Partner
3 1 1

This is an accepted solution.

Hi Evita, 

 

I tried loading the dependencies and adjusting the code but was still unable to get it working. I managed to use axios and followed the instructions in their documentation and got this working (https://www.npmjs.com/package/axios).

 

Thanks for you help.