Problem creating a Strava webhook subscription using Google Apps Script - google-apps-script

EDIT 2 - I have now provided my own answer to the issue - any further thoughts or inputs would still be appreciated
EDIT 1 - POTENTIAL RELEVANT INFORMATION:
I've found this footnote at the bottom of the Content Service documentation page that says:
For security reasons, content returned by the Content service isn't served from script.google.com, but instead redirected to a one-time URL at script.googleusercontent.com. This means that if you use the Content service to return data to another application, you must ensure that the HTTP client is configured to follow redirects. For example, in the cURL command line utility, add the flag -L. Check the documentation for your HTTP client for more information on how to enable this behavior.
This seems relevant as I am using ContentService to serve data to Strava's GET request - would this not mean that the response it receives comes from a different URL and hence not from the specified callback URL?
I've been trying to use Google Apps Script to create a webhook subscription to Strava and I feel like I'm extremely close to figuring it out, but I've encounter one last hurdle that I can't seem to pass.
The documentation for creating a webhook subscription to Strava is listed here and I've reached the stage at which I'm making a POST to the Strava API requesting a subscription. Strava then sends an HTTP GET request to my callback_url that I've specified which contains some parameters - most important of which is the hub.challenge parameter. This param must then be sent back within 2 seconds by my callback address in order for the link to be validated. This is where I'm having some grief.
function doGet(e) {
var hubChal = e.parameter["hub.challenge"];
var result = {
"hub.challenge": hubChal
};
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
}
Above is my current function that is dealing with incoming GET requests.
In the documentation it states:
Your callback address must respond within two seconds to the GET request from Strava’s subscription service. The response should indicate status code 200 and should echo the hub.challenge field in the response body as application/json content type: { “hub.challenge”:”15f7d1a91c1f40f8a748fd134752feb3” }
However, when I send the POST to the webhook subscription API I receive this response on Postman:
{
"message": "Bad Request",
"errors": [
{
"resource": "PushSubscription",
"field": "callback url",
"code": "GET to callback URL does not return 200"
}
]
}
I checked through the troubleshooting advice that is offered on the doc page, an element of which gives a sample GET request for you to send yourself to see what your callback address offers in return:
Check that the response to the above request shows a 200 status and correctly echos the hub.challenge in the JSON body. The response body to the above sample curl request should look like { “hub.challenge”:”15f7d1a91c1f40f8a748fd134752feb3” }
With the following dummy GET request:
{your-callback-url}?hub.verify_token=test&hub.challenge=15f7d1a91c1f40f8a748fd134752feb3&hub.mode=subscribe
Plugging in my callback url and sending a GET request via Postman returns the following to me:
{
"hub.challenge": "15f7d1a91c1f40f8a748fd134752feb3"
}
Along with showing a 200 OK Status code and in well under 2 seconds.
I really can't see what I've done incorrectly here, as it seems like I'm fulfilling the criteria laid out to set up a subscription. It's worth mentioning that I'm not very familiar with Google Apps Script, so it's entirely possible and even likely that I'm missing something basic, but I can't see it for the life of me.
I've gone through all of the troubleshooting advice and haven't been able to find an answer online despite an entire afternoon and evening of searching yesterday. Any help would be enormously appreciated - thanks.

I believe I've finally figured out my issue and unfortunately it seems it is not possible to set up a webhook subscription to Strava using Google Apps Script - at least not whilst using ContentService to serve data to Strava GET requests.
At the end of the ContentService documentation it says:
For security reasons, content returned by the Content service isn't served from script.google.com, but instead redirected to a one-time URL at script.googleusercontent.com. This means that if you use the Content service to return data to another application, you must ensure that the HTTP client is configured to follow redirects. For example, in the cURL command line utility, add the flag -L. Check the documentation for your HTTP client for more information on how to enable this behavior.
The problem with this is that Strava expects and will only accept a 200 status code attached to the response to its GET request, however ContentService sends a 302 redirect to this one-time URL. I was able to verify this in Postman by turning off "Automatically follow redirects" in the request settings of my dummy GET request:
https://i.stack.imgur.com/vAvIm.png
Since I have auto redirect enabled by default on Postman, I wasn't seeing the 302 at any point when sending my own requests, leading to everything appearing to be in order.
If there is a way around this ContentService issue then please do inform me, as the outcome of this is that I'm now going to have to poll Strava for new data (booo) which will make my finished product significantly more clunky.
Hopefully this post will now stand to help anyone else who finds themselves with my issue in the future and will save them a few days of confusion!

I am not familiar with Goole Apps Script (very cool, by the way!), but it sounds like you have Strava webhooks well understood.
It sounds like your doGet method is returning something other than 200. Do you have a logging feature that can output what the doGet method is being sent, what it's sending back, etc?
If you haven't already, you may want to check out the Strava tutorial on subscribing to webhooks using Node, Express and Ngrok. A few extra steps, but fairly easy to follow and debug.

Related

Google Apps Script return 405 for POST request

I have a problem with handling POST requests in Google Apps Script. I've created simple project with following functions:
function doGet(e){
return ContentService.createTextOutput("test").setMimeType(ContentService.MimeType.TEXT);
}
function doPost(e){
return ContentService.createTextOutput("test").setMimeType(ContentService.MimeType.TEXT);
}
When I try to send a GET request from postman I get correct response - as expected. However when I try to send POST request I get 405 Method not allowed and HTML error page in response. In deployment settings I set that it should execute as me and should be accessible to everyone.
What am I missing? How to make POST requests work with Google Apps Script?
EDIT:
So as Heiko Theißen wrote below there is 302 redirect at first.
As I can see Postman follows that redirect and sends POST once again to new URL but this request fails and I still don't know why unfortunately. I can see in security section that there is header Allow: HEAD, GET.
I cannot see any preflight request from Postman (as TheMaster suggested).
About reproducibility: I've pasted complete content of google apps script, and I mentioned that I am making request from postman. Here is link to current deployment of that script: https://script.google.com/macros/s/AKfycbyHdVpclM7pH1BB3IzwNjtcH07DF75H8ldqeLQCwQnX71lMs371g-UO-i8JaI5_zRqrDw/exec
Also here is screenshot from Postman - I'm just sending empty POST request without any custom headers. Tried also sending plain text or json as payload but results were the same each time.
So back to my original question: How can I receive POST request successfully in google apps script deployed as Web App?
I make it workable from postman by disabling the annotated option from settings.
If the preflight explanation given by TheMaster does not solve your problem, the following might help:
Requests to Google Apps Script always happen in two stages: The first request draws a redirection response to a generated URL, and the second request to that URL draws the response that you programmed (the text output "test").
When the first request is a POST request, the redirection response has status "302 Found", and the specification is ambiguous about what method the second request should have:
Note: For historical reasons, a user agent MAY change the request
method from POST to GET for the subsequent request. If this
behavior is undesired, the 307 (Temporary Redirect) status code
can be used instead.
Google Chrome makes the second request as a GET request (without repeating the POST payload, which the server already knows under the generated URL) and this works.
However, if your browser or Postman client does not change the method and makes the second request again as a POST (and your screenshot shows this is true), the server does not accept this and responds with "405 Method Not Allowed".
In other words: Google Apps Script expects the second request to be a GET request, but not all clients behave like that, because the specification is ambiguous at this point. Workarounds:
You can influence the behavior of Postman so that it does not preserve the POST method between the first and second request. See here.
Google Apps Script could avoid the ambiguity by responding to the first request with "303 See Other", but it does not. Perhaps create an issue for that?

Connecting a Hello sign API to apps script project

I want to Create a new API app usin Google Apps script and Hello sign API
I have specified the redirect URL as https://script.google.com/macros/s/AKfycbyKw3oLmpqINGsDml281iUbxBboKn950dqVFXNibMfLurxYcRPf/exec and the screenshot is shown below
Also, the code of the apps script file is
function doPost(e) {
return ContentService.createTextOutput("Hello API Event Received.");
}
The documentation says: https://app.hellosign.com/api/eventsAndCallbacksWalkthrough
I get error message as shown like here
Illustation image here
405(http-status-code-405) is "method not allowed", where a incorrect method is used. In this case, ContentService uses a specific pattern of redirection (post-redirect-getwiki), where the POST request to script.google.com is redirected(302) to a one time url at script.googleusercontent.com, to which a GET request should be made.
302 specification did not intend the method to change from POST to GET, but this pattern is very common in the web. But, hellosign-api seems to make another POST request to the one time redirected url at script.googleusercontent.com. There isn't much you could do from apps script to change this behavior. It is possible to change to HtmlService to avoid the redirection, but Hellosign specifically requires you to provide a specific text content as response: Hello API Event Received. This isn't possible with HtmlService.
You could make a feature request/bug fix request to Google to change redirect status to 303, where method change to GET is explicitly specified. Or Make a request to Hellosign to follow 302 redirects with GET request, as that is the most common way, things are done in the web.
References:
Curl redirect preferred method -L
ContentService Redirect
Post redirect get wiki
RFC7231 § 6.4.3
RFC7231 § 6.4.4
Looking at our backend logs, we see that your callback URL is not allowing our POST call to be granted to fetch "Hello API Event Received" response. This can be due to they way your [callback handler is set up][1].
Would you mind taking a look at this similar ask and they way they tackled the POST/Allow header field and let us know how it goes by emailing at apisupport#hellosign.com?
App Script sends 405 response when trying to send a POST request
[1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,supported%20by%20the%20target%20resource.

How do I send an http POST response that will work with chatbot using GAS

I am trying to create a webhook for a chatbot to pull information from Google Sheets. Since Google has Google Apps Script that easily works with sheets, I thought I would try running my scripts there. I have managed to work a GET request as required by chatbot returning a string. But I cannot seem to connect the POST request to relay information.I have logged the request and response which look correct and make me think my script is running smoothly, but that there must be something wrong in the http connection.
The problems I am finding are that GAS sends back a redirect which could be the problem. Is there a way around this to just send a JSON string response?
Also, when performing the POST request on chatbot, I get an error:
Webhook "GAS Order Data" was not executed
Request failed with status code 405
I looked up 405 error and says that
Error 405 : HTTP method is not allowed by a web server for a requested URL
What would be a way to know where my error is?
my other option is aws lambda, but haven't gotten that to work either.
This is my doPost function. Everything runs perfectly. It should return a string, but ContentService actually replies with a 302 redirect (tested with talend api tester). My log functions save information to a log sheet since I can't use the script log.
//get order information from an order id
function doPost(e) {
startLog(['- NEW POST REQUEST - v'+ver, new Date()]);//e.postData.contents
var content=JSON.parse(e.postData.contents);
addLog(JSON.stringify(content));//log input
if(!verify(e.parameter.token)){//part of chatbot to validate url
addLog("Token Invalid");
return ContentService.createTextOutput("Token Invalid");
}
var response = processPost(content.result.resolvedQuery);//analyze data (return json object)
addLog(JSON.stringify(response));
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.TEXT);
}

Testing Google Script with PostMan

I have created a Google Script and published it as a web app, very much like this post has.
However, I would like to call my extremely simple doGet method using Postman. The web app is published with anonymous access and execution permission as me.
I was expecting to be able to paste the URL into Postman, set the GET verb and hit Send - and to see the same result I see in the browser. I do not.
What am I doing wrong?
[UPDATE] Responding to comment by themaster
I have created a Google Apps Script called devices in my Google Drive. I've added this function:
function doDelete(e) {
return HtmlService.createHtmlOutput('{"test":"yes"}');
}
Simple, I know, but should respond to a DELETE request with:
{
"test": "yes"
}
I then hit Save, followed by Deploy > Publish as web app... with the following options:
Project version: new
Execute the app as: me
Who has access: Anyone, even anonymous
I hit Update and get a URL like this:
https://script.google.com/macros/s/ABcdefgHInmLDGiHmpGmXkXIxMjsh0s61sKZ9ov6OOSpkb--1quTtfM/exec
If the function is named doGet and I paste the URL into the browser, I see this JSON mentioned above.
If I leave the function named doDelete and I make a DELETE request from Postman, I get this:
Could not get any response There was an error connecting to
https://script.google.com/macros/s/ABcdefgHInmLDGiHmpGmXkXIxMjsh0s61sKZ9ov6OOSpkb--1quTtfM/exec.
Why this might have happened: The server couldn't send a response:
Ensure that the backend is working properly Self-signed SSL
certificates are being blocked: Fix this by turning off 'SSL
certificate verification' in Settings > General Proxy configured
incorrectly Ensure that proxy is configured correctly in Settings >
Proxy Request timeout: Change request timeout in Settings > General
If I change the function to doGet, republish and call it with a GET request from Postman, I get the same result.
[EDIT] Clarification
If I make the Postman call using GET and have the doGet function setup to call an IFTTT webhook, the webhook does fire. I can also make it fire using the doPost and a POST request in Postman.
However, if I use doDelete with a DELETE request in Postman, the IFTTT webhook does not get called.
Regardless of the verb used in Postman I do not get a response - only the message quoted above.
[EDIT] Response to #sourabh-choraria question
My code currently is just this:
function doGet(e) {
return HtmlService.createHtmlOutput('{"valid":"no"}');
}
Published with the process described above, I get this when calling with Postman:
Could not get any response
I am making that request as a GET without any headers.
Currently, Google Apps Script's Web Apps only support the following HTTP methods, as per their requirements for web apps -
GET via doGet()
POST via doPost()
While not explicitly stated in the reference doc that PUT, DELETE, UPDATE etc. are unsupported, there is no way to execute those HTTP methods via Web Apps in Apps Script.

Set up Google Calendar V3 API Push notification via my Google Apps script

I want to use Google CAL API v3 to watch multiple calendars and thus to update all those events from those calendars to a Master Calendar, so I need to use WATCH/PUSH notification in order that whenever there is a new event in those cal’s, those events shd be automatically be pushed/created in my master calendar.
But, I am facing the following issue :Under APIs Explorer. Most of the services return 200 OK, except the Watch request not working.
Services > Calendar API v3 > calendar.events.watch
i got the below request and response
Request
POST https://www.googleapis.com/calendar/v3/calendars/mailid%40domain.com/events/watch?key={YOUR_API_KEY}
{
}
Response
400 OK
{
"error": {
"errors": [
{
"domain": "global",
"reason": "required",
"message": "entity.resource"
}
],
"code": 400,
"message": "entity.resource"
}
}
And then i followed as per the below google documentation
https://developers.google.com/google-apps/calendar/v3/push
Registering Your domain:
Step 1: Verify that you own the domain
Step 2: Register your domain
I verified my domain/notifications in google webmaster tools.
My verified domain look like this:
http://my-domain.com/notifications
And have added my domain name in the webhooks under the PUSH in Google Developer Console, as explained in the doc.
Is there anything i am missing here, plz is there an issue in SSL certificate fr my domain, how can i atleast test in the API explorer, so that i get a return code 200 OK. After that, I need to implement this push/WATCH via Google Apps Script.
Is there any documentation for apps script to use push notification.
Any hint, tip will help me, I am tired with this for the past 1 week in trial and error method.
In the request that you posted the body is empty. In the documentation there is an example where you need to provide the calendar ID, the web_hook and other information:
{
"id": "01234567-89ab-cdef-0123456789ab", // Your channel ID.
"type": "web_hook",
"address": "https://example.com/notifications", // Your receiving URL.
...
"token": "target=myApp-myCalendarChannelDest", // (Optional) Your channel token.
"expiration": 1426325213000 // (Optional) Your requested channel expiration time.
}
Add this information when creating the push notification.
For using apps script you can use URLfetch service with the method "fetch()" then you would need to provide the parameters mentions and make the call as a POST.
Hope this helps.
UPDATE: I will write here to have more space.
Push notifications work in this way.
-You create a push notification with the id, the resource you want to watch, the address where you want to receive the notification and the optional parameters.
-if successful, the server will watch that resource and will send a notification to the address mentioned in the request describing the changes when a change is made to the resource.
-The server needs to know that you received the notification, so from the address mentioned in #1, you need to tell the server that you received the notification returning a HTTP 200 response. (for this you need an app that is listening to the notifications and sending the responses)
for your questions
1.- As the server will make a HTTP request to a url (your web_hook address) Google requires it to be secure, that's why it is required to be HTTPS.
2.- Yes, without that url you won't be able to receive the notification.
3.- Google also needs to verify that the url you provided is actually yours, so you will need to provide the url, then Google will provide you a file, you have to make this file available in that url, then Google will call the url an will retrieve that file. Then you can prove that the URL is yours. I'm not sure if this step can be done with apps script. You can actually deploy a script as web app but i'm not sure if you can use it for this purpose.(here you can find more info and different ways to verify the url)
4.- That's created by yourself. In the documentation is pointed out the recommendations for it.
I hope this clarifies your questions.
You can check here how push notifications work, this example is for Drive but the concept is the same.