Received Webhook in Google App Script - Shopify not receiving my 200 OK

Michael_Holmes
New Member
12 0 0

I am using this code in Google App Script to receive a webhook created in the Shopify Admin > Notifications

The code executes in about 500 milliseconds. But I can see that Shopify is resending the post again because firebase is being triggered multiple times in succession. My webhook has been deleted 3 times because I'm not responding correctly apparently.

function doPost(e) {
//store data  in firebase
return ContentService.createTextOutput('200 OK');
}

What am I doing wrong here?

 

Documentation: https://developers.google.com/apps-script/guides/web

 

Replies 17 (17)

Michael_Holmes
New Member
12 0 0

(Further proof)
I sent a test post to my webapp using zapier and got this response which seems correct to me. Shopify seems to still be sendnig the same post multiple times.

Michael_Holmes_0-1606349504999.png

 

Michael_Holmes
New Member
12 0 0

any help on this? I'm returning 200 OK as text in 0.5 second but its still deleting my webhook.

policenauts
Shopify Partner
206 10 66
TechGuy
Tourist
6 0 2

I'm a bit late to the party, but could it possibly be due to the way ContentService uses redirects? See here: GAS Content Service -> Redirects 

I haven't gotten far enough into using the Shopify API to test this on my own, but HtmlService a try instead: GAS Class HtmlService -> createHtmlOutput() 

Michael_Holmes
New Member
12 0 0

Seems this sovles my problem:

 

 

 

HtmlService.createHtmlOutputFromFile('200 OK').setSandboxMode(HtmlService.SandboxMode.IFRAME);

 

 

 

TechGuy
Tourist
6 0 2

Well, that's good to know! I'll keep that in mind when I'm getting set up.

Another issue is that there's no way to read HTTP headers in Google Apps Script (an annoyance of mine for a long time...), and that's where Shopify puts all the context (including the hash so you know the webhook is truly from them). I don't think sending a hash through the query parameter is secure, but at least we can add some context that way and have the script parse the query string parameter when received (eg: {{web app url}}/exec?api_version=2020-10&scope=orders_create&store=samplestore).

TechGuy
Tourist
6 0 2

For anyone having troubles, this worked well, and the query parameters help to identify where the webhooks are coming from. Note that there's no need for text in the HtmlService response; Shopify just looks at the headers for a 200 OK response code.

/*
Sample format: https://script.google.com/macros/s/{{SCRIPT_ID}}/exec?source=shopify&api_version=2020-10&store_name={{STORE_NAME}}&event={{EVENT_NAME}}
*/
function doPost(e) {
  
  if (e && e.postData && e.postData.type === "application/json") {
    let data = JSON.parse(e.postData.contents);
    let query = e.parameter;
    if (query.source === "shopify") {
      Logger.log("Shopify API " + query.api_version + ": Received " + query.event + " webhook data for store " + query.store_name + ".");
      // TODO: data stuff
    }
  }
  
  return HtmlService.createHtmlOutput("");
  
}

 

When I tried it with ContentService.createTextOutput() I got several hits of the doPost function indicating that Shopify retried it a few times. Checking with Chrome developer tools it appears that ContentService definitely does do a redirect that Shopify doesn't like, so it's best not to use that. ContentService doesn't sanitize the output like HtmlService does, so that's why Google needed to go through an extra step.

policenauts
Shopify Partner
206 10 66

@TechGuy I'm still having trouble with this as Shopify is still sending multiple app/uninstalled webhooks, usually over the course of a few days. Here is my code, any idea what I'm doing wrong?

 

function doPost(e) {
  
  // get the payload 
  var postData = e.postData.contents;
  var json = JSON.parse(postData);
  var stringified = JSON.stringify(json, null, 2)
  
  GmailApp.sendEmail('MY EMAIL', 'webhook, someone uninstalled the app', stringified);
  
  return HtmlService.createHtmlOutput("");

}

 

TechGuy
Tourist
6 0 2

@policenauts, I don't see any issues with your code. It's simple and should work reliably, and even if it were to fail on Google's side, Google still sends a 200 response code.

Are you 100% sure that the webhooks are being resent, and they aren't all unique instances, possibly where the same party uninstalled the app a second time? I don't know what the payload is like from Shopify for that event, so I don't know if you can tell from that data.

policenauts
Shopify Partner
206 10 66

@TechGuy Thanks for the quick response - your note made me look more closely at the POST bodies sent, and I'm seeing now that the two requests are indeed different. It seems in my case Shopify first sends an uninstall webhook at the moment of uninstall with the entire shop info of the store (name, address, etc.) in the body, and then 48 hours later sends a webhook featuring only shop_id and shop_domain properties. Is that expected behavior?

TechGuy
Tourist
6 0 2

@policenauts Hmm, that seems really strange, but I'll be honest I'm very new to the Shopify world so I don't know why that would be. I hope someone else will have some clarity on that, but at least for now you'll have the option to ignore webhooks that aren't fully formed by adding a test (if these 'lite' webhooks truly are just repeats from prior events).

 

function doPost(e) {
  
  // get the payload 
  var postData = e.postData.contents;
  var json = JSON.parse(postData);

  if (json.name) {
    var stringified = JSON.stringify(json, null, 2)
    GmailApp.sendEmail('MY EMAIL', 'webhook, someone uninstalled the app', stringified);
  }

  return HtmlService.createHtmlOutput("");

}

 

policenauts
Shopify Partner
206 10 66

@TechGuy @Michael_Holmes sorry guys, I got mixed up. The one I'm receiving 48 hours later is actually the shop_redact webhook. Please disregard! 

TechGuy
Tourist
6 0 2

@policenauts Ahh, that makes sense! Another reason why Shopify should include more details about the webhook event type in the payload. 😉

 

So, if you look at my suggestion from way back of adding query parameters in your webhook URL, you can programmatically detect which event type you're receiving even when you can't read the HTTP header. That would be a good idea if you're subscribed to more than one webhook.

MPJD
Tourist
4 0 1

All the solutions here seem to not work for me. I was still getting a hook repeat if I can call it that, but the below solution worked for me. For those still struggling hope this helps.

var response = {
    "statusCode": 200,
    "headers": {'Content-Type':'application/json'},
    'body': 'the body'
  }
 return HtmlService.createHtmlOutput(response);

this is just a snippet but hopefully that makes sense 

Parasoul
Shopify Partner
15 1 6

@MPJD Thank you. This seems to have worked for us

MPJD
Tourist
4 0 1

Glad the solution worked for you. While we are on this topic, I would also like to suggest using async which apps script has added in natively a while after this post. which could also help

Michael_Holmes
New Member
12 0 0
HtmlService.createHtmlOutputFromFile('200 OK').setSandboxMode(HtmlService.SandboxMode.IFRAME);

Have you tried this code? Not sure if it's any different but it works for me. 

Few other ideas

- I used to use Shopify Admin > notifications, but now I create webhooks using the API. 

- Did you try testing with https://webhook.site/ or postman?