I'm trying to POST to a web service that is expecting to get JSON as payload using Google Apps Script. I'm using the following code:
var options =
{
"method" : "post",
"contentType" : "application/json",
"headers" : {
"Authorization" : "Basic <Base64 of user:password>"
},
"payload" : { "endDate": "2012-06-03" }
};
var response = UrlFetchApp.fetch("http://www.example.com/service/expecting/json", options);
On the server side I'm getting the following error:
WARN [facade.SettingsServlet] 04 Jun 2012 15:30:26 - Unable to parse request body: endDate=2012-06-03
net.liftweb.json.JsonParser$ParseException: unknown token e
I'm assuming that the server is expecting to get
{ "endDate": "2012-06-03" }
instead of
endDate=2012-06-03
but I don't know how to make the UrlFetchApp do it.
I do not understand the server side error but the 'payload' parameter must be a string as specified here: https://developers.google.com/apps-script/class_urlfetchapp?hl=fr-FR#fetch.
try:
var options =
{
"method" : "post",
"contentType" : "application/json",
"headers" : {
"Authorization" : "Basic <Base64 of user:password>"
},
"payload" : '{ "endDate": "2012-06-03" }'
};
If you set payload as a String, it will be passed directly (as a
UTF-8 string).
If you set payload as an Object, it will be sent like
an HTML form (which means either 'application/x-www-form-urlencoded' if the
fields are simple, or 'multipart/form-data' if the Object includes a
blob/file).
For your use case (the server is expecting to receive JSON), it sounds like Utilities.jsonStringify() is the way to go.
Something like this worked for me in a similar situation:
Instead of creating payload and adding to options, I built the parameters into a string to append to the URL:
var params = "id=2179853&price=62755";
then, I appended params to the url string:
var result = UrlFetchApp.getRequest(url + '?' + params, options);
So, my options was passed containing only the header.
For my project, this worked like a charm.
Here goes the code that should work with some important comments:
function testMe() {
var products_authkey = "------------";
try {
var url = "https://app.ecwid.com/api/v1/---------/product?id=----------&secure_auth_key=" + products_authkey;
//url= "http://requestb.in/----------"; // you can actually debug what you send out with PUTs or POSTs using Requestb.in service
var payload = {
id: "21798583", // id is necessary and should be a string, or it might be sent in scientific representation (with E)
price: 62755
};
payload = JSON.stringify(payload); // the payload needs to be sent as a string, so we need this
var options = {
method: "put",
contentType: "application/json", // contentType property was mistyped as ContentType - case matters
payload: payload
};
var result = UrlFetchApp.getRequest(url, options);
Logger.log(result) // a better way to debug
var result = UrlFetchApp.fetch(url, options); // works perfectly in my case
Logger.log(result)
} catch (e) {
Logger.log(e)
}
}
Related
Goal is to update "OrderStatusID" of order to "1" using urlfetchapp with a put request via Apps Script.
function updateOrderStatus(){
var private_key = "{private_key}";
var merchant_token = "{merchant_token}";
var secure_url = "{secure_url}";
var body = JSON.stringify({"OrderStatusID": "1"});
var url ="https://apirest.3dcart.com/3dCartWebAPI/v2/Orders/{orderID}";
var options = {
"method" : "put",
"headers" : {
"Content-Type" : "application/json",
"Content-Length" : body.length,
"Accept" : 'application/json',
"SecureURL" : secure_url,
"PrivateKey" : private_key,
"Token" : merchant_token
},
"body" : body,
"muteHttpExceptions" : false,
}
try{
var response = UrlFetchApp.fetch(url, options);
}
catch(err){
Logger.log(err);
}
finally{
Logger.log(response);
}
}
Code throws error Exception: Attribute provided with invalid value: Header:Content-Length
Code altered to remove sensitive information.
Changing the name of the option "body" to "payload" resolved the issue.
The answer was found in this google issue tracker thread.
ek...#googlers.comek...#googlers.com #6Apr 11, 2016 07:03AM Status:
Won't Fix (Not Reproducible) UrlFetchApp doesn't have an advanced
parameter called 'contentLength'. The Content-Length header is
automatically calculated based on the length of the payload passed in.
The 'contentLength' advanced parameter you set in your sample is
simply ignored by the backend and the length automatically calculated.
The Content-Length is automatically calculated from the payload. I thought it was supposed to be named body because the 3dCart API documentation was using body as the name of its json in the example.
Corrected Code:
function updateOrderStatus(){
var private_key = "{private_key}";
var merchant_token = "{merchant_token}";
var secure_url = "{secure_url}";
var body = JSON.stringify({"OrderStatusID": "1"});
var url ="https://apirest.3dcart.com/3dCartWebAPI/v2/Orders/{orderID}";
var options = {
"method" : "put",
"headers" : {
"Content-Type" : "application/json",
//"Content-Length" : body.length,
"Accept" : 'application/json',
"SecureURL" : secure_url,
"PrivateKey" : private_key,
"Token" : merchant_token
},
"payload" : body,
"muteHttpExceptions" : false,
}
try{
var response = UrlFetchApp.fetch(url, options);
}
catch(err){
Logger.log(err);
}
finally{
Logger.log(response);
}
}
I'm working with the Coinbase Pro API and while it's working well for GET requests I'm getting an "Invalid signature" when I try a POST request to place an order. I suspect that this may be something related to the "body" of the message since this is the only difference with the GET where "body" is empty.
I'm wondering if someone can help me on this.
function SellMarket (product,amount) {
var requestPath = '/orders';
var method = 'POST';
var body = JSON.stringify({
"type": "market",
"side": "buy",
"product_id": product,
//"size": amount,
'size': '1.0'
});
Logger.log('Sell - Body = '+body);
var responseJson = SignAndCallAPI(method, requestPath, body);
Logger.log('Sell Executed = '+responseJson);
}
function SignAndCallAPI(method, requestPath, body) {
var timestamp = Math.floor(Date.now() / 1000).toString();
var what = Utilities.base64Decode(Utilities.base64Encode(timestamp + method + requestPath + body));
var decodedsecret = Utilities.base64Decode(globalvars_CB.secret);
var hmac = Utilities.base64Encode(Utilities.computeHmacSha256Signature(what, decodedsecret));
var options = {
'method' : method,
'muteHttpExceptions' : true,
'headers' : {
'Content-Type': 'application/json',
'CB-ACCESS-KEY' : globalvars_CB.apikey,
'CB-ACCESS-SIGN' : hmac,
'CB-ACCESS-TIMESTAMP' : timestamp,
'CB-ACCESS-PASSPHRASE' : globalvars_CB.passphrase,
}
}
var responseJson = UrlFetchApp.fetch(globalvars_CB.uri+requestPath, options);
return(responseJson);
}
Untested suggestion, I recommend changing this bit in your code (in SignAndCallAPI function):
var options = {
'method' : method,
'muteHttpExceptions' : true,
'headers' : {
'Content-Type': 'application/json',
'CB-ACCESS-KEY' : globalvars_CB.apikey,
'CB-ACCESS-SIGN' : hmac,
'CB-ACCESS-TIMESTAMP' : timestamp,
'CB-ACCESS-PASSPHRASE' : globalvars_CB.passphrase,
}
}
to:
const options = {
method: method,
payload: body,
contentType: 'application/json',
muteHttpExceptions: true,
headers: {
'CB-ACCESS-KEY': globalvars_CB.apikey,
'CB-ACCESS-SIGN': hmac,
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-PASSPHRASE': globalvars_CB.passphrase,
},
};
due to the reasons below:
Although you pass body to SignAndCallAPI function and use body in HMAC computation, you don't appear to actually include body in the POST request sent to the server. I was expecting to see it as the value to a payload property of your options object.
UrlFetchApp documentation (https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetchurl,-params) seems to indicate that Content-Type should be specified in the contentType property of your options object (as opposed to explicitly including a Content-Type header).
I've not looked at the Coinbase API documentation in detail (https://docs.pro.coinbase.com/#place-a-new-order), but body seems okay. You'll also want to make sure the HMAC computation for the CB-ACCESS-SIGN request header is as described in the documentation (https://docs.pro.coinbase.com/#signing-a-message).
I'm just trying to make a simple postMessage call from a google apps script with an image attached, but I get the following response:
"{"ok":false,"error":"invalid_arg_name"}"
Here is the function that creates the payload:
function getPostMessagePayload(fileUrl) {
var content = {
"channel":"#data-vis",
"token": ACCESS_TOKEN,
"text":"Chart update:",
"attachments": [
{
"title": "Chart",
"fallback": "Fallback",
"text": "Testing chart",
"image_url": fileUrl
}
]
};
return content;
}
And here is where I make the request:
var POST_MESSAGE_ENDPOINT = 'https://slack.com/api/chat.postMessage';
function performPostMessage(payload) {
var res = UrlFetchApp.fetch(
POST_MESSAGE_ENDPOINT,
{
method: "post",
payload: JSON.stringify(payload),
muteHttpExceptions: true,
}).getContentText();
return res;
}
It's impossible to tell what the actual problem is. I've tried making my token obviously incorrect, the URL obviously incorrect, and deleting/adding random args and it gives the same response every time.
When I use the webhook to do this rather than the API, it works fine.
My app has the following permissions in Slack:
chat:write:bot
incoming-webhook
Problem
You are sending a JSON object as payload with your POST request, whilst the contentType parameter of the fetch() method is defaulted to application/x-www-form-urlencoded.
Solution 1
In addition to JSON.stringify(), to ensure the payload is sent correctly, wrap it in an encodeURIComponent() built-in function. If the issue persists, continue to solution 2.
Update to solution 1
Nearly forgot how fetch() method treats objects passed to payload with default x-www-form-urlencoded content type. Remove the JSON.stringify() entirely (and add encodeURI() / encodeURIComponent() if needed).
Solution 2
Slack API supports application/json content type of POST requests. In your case it might be easier to send the request with contentType parameter set to application.json (note that you will have to move authorization from payload to headers):
//fetch part;
var res = UrlFetchApp.fetch(
POST_MESSAGE_ENDPOINT,
{
method : 'post',
contentType : 'application/json',
headers : {
Authorization : 'Bearer ' + ACCESS_TOKEN
},
payload : JSON.stringify(payload),
muteHttpExceptions : true,
})
//payload part;
var payload = {
"channel" : "#data-vis",
"text" : "Chart update:",
"attachments" : [
{
"title" : "Chart",
"fallback" : "Fallback",
"text" : "Testing chart",
"image_url" : fileUrl
}
]
};
Useful links
fetch() method reference;
postMessage method reference (Slack API);
I have a google script running as a webapp to handle the backend of a slack app.
The app has been Authenticated and I have the OAUTH token from this.
I can currently post to a channel with button actions using the chat.postMessage using the for-mentioned token.
Actions url points back at my webapp and hook in via doGet, from this response i construct a JSON object.
var response_payload = {
"token" : access_token,
"ts" : message_ts,
"channel" : channel_id,
"text" : "Approved! you are a winner!"
})
response_url = "https://slack.com/api/chat.update";
sendToSlack_(response_url, response_payload)
posted via the following function:
function sendToSlack_(url,payload) {
var options = {
"method" : "post",
"contentType" : "application/json;charset=iso-8859-1",
"payload" : JSON.stringify(payload)
};
return UrlFetchApp.fetch(url, options)
}
however returned is the following:
{"ok":false,"error":"not_authed"}
I can't find any documentation about this error other than the following
Sending JSON to Slack in a HTTP POST request
However this is in regard to a chat.postMessage request of which in my implementation is working correctly.
You need to put token to header instead of json payload if using application/json. Here is doc for this.
So you request should look like this:
POST /api/chat.update HTTP/1.1
Authorization: Bearer xoxp-xxx-xxx-xxx-xxx
Content-Type: application/json;charset=UTF-8
{
"channel": "xxx",
"text": "Hello ~World~ Welt",
"ts": "xxx"
}
Note: there is no token field in payload.
Well according to the link your provided, Slack does not accept JSON data (weird).
Also, after playing around with their tester, Slack seems to be doing a GET request on https://slack.com/api/chat.update with query parameters attached like this:
https://slack.com/api/chat.update?token=YOUR_TOKEN&ts=YOUR_TIME&channel=YOUR_CHANNEL&text=YOUR_TEXT_URL_ENCODED&pretty=1
So use this code:
var response_payload = {
"token" : access_token,
"ts" : message_ts,
"channel" : channel_id,
"text" : "Approved! you are a winner!"
}
function httpGet(theUrl)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", theUrl, false ); // false for synchronous request
xmlHttp.send( null );
return xmlHttp.responseText;
}
response_url = encodeURI("https://slack.com/api/chat.update?token=" + response_payload['token'] +
"&ts=" + response_payload['ts'] + "&channel=" + response_payload['channel'] + "&text=" + response_payload['text'] +"&pretty=1");
httpGet(response_url);
Ok! thankyou all for your input.. certainly I have learned a little more. After tweeting at slack_api my original code more or less worked as is.. I had to JSON.parse(payload); the payload in order to then access the object parameters within.. the full example is as below.
function post_update(url, payload) {
var options =
{
'method': 'post',
"payload" : payload,
};
var result = UrlFetchApp.fetch(url, options);
return result.getContentText();
}
function doPost(e) {
var payload = e.parameter.payload;
var json = JSON.parse(payload);
response_url = "https://slack.com/api/chat.update";
// get object elements
var action = json.actions[0].value;
var user = json["user"].name;
var message_ts = json["message_ts"];
var channel_id = json["channel"].id;
if (action == 'approved') // payload if action is 'approved'
{
var response_payload = {
"token" : access_token,
"ts" : message_ts,
"channel" : channel_id,
"text" : "Approved! *" + invitation_name + "* has been sent an invite!",
"attachments" : JSON.stringify([{
"text": ":white_check_mark: Approved by #" + user,
}])
}
}
if (action == 'denied') // payload if action is 'denied'
{
var response_payload = {
"token" : access_token,
"ts" : message_ts,
"channel" : channel_id,
"text" : "Denied. *" + invitation_name + "* has been declined an invite",
"attachments" :JSON.stringify([{
"text": ":exclamation: Declined by #" + user,
}])
}
}
post_update(response_url, response_payload);
return ContentService.createTextOutput().setMimeType(ContentService.MimeType.JSON);
}
I need to upload files to a 3rd party service. The files are created on Google Drive, and I get the blob-data for it.
I want to create a multipart request which now looks like this when I do the UrlFetchApp Post.
This is the payload as string. I have some code that generates the payload. Getting it properly formatted is not the problem, it's the format it is supposed to be.
-----------------0.13accb4c42d338
Content-Disposition: form-data; name="source"; filename="Zzapps.jpg"
Content-Type: application/octet-stream
[[[IMAGE DATA HERE -- OMITTED FOR BREVITY]]]
-----------------0.13accb4c42d338
Content-Disposition: form-data; name="filename"
Zzapps.jpg
-----------------0.13accb4c42d338--
This is the piece of code that does the UrlFetchApp command.
var authHeaders = {
Authorization: 'OAuth2 '+access_token
}
var params = {
accept: "application/json",
method: 'POST',
payload: payload,
contentType: 'multipart/form-data; boundary='+boundaryKey,
headers: authHeaders,
ContentLength: payload.length,
muteHttpExceptions: true
}
var resx = UrlFetchApp.fetch(url, params);
The recieving party gives an error (missing source). I am not sure if my multipart-post is okay in the first place, I do not find any testing URL's to check if I do a proper upload.
How could I send blob-data as a multipart upload the proper way?
And now I use blob.getDataAsString() <-- is this correct ?
If you're building the payload string yourself, you want to do blob.getBytes(), not blob.getDataAsString().
However, there's an easier way. Rather than building the payload string yourself, you can just set a javascript object as the payload, and UrlFetchApp will automatically generate the appropriate payload string, select the correct content-type, boundary, and most of the other options. Just use the "name" attribute of the HTML form's input fields as your object keys. For files, use blobs as the key's values.
function sendReportToSteve() {
var url = "https://example.com/stevedore.html";
var form = {
date : new Date(),
subject : "Happy birthday!",
comment : "quakehashprismkeepkick",
attachment1 : DriveApp.getFileById("sH1proy0lradArgravha9ikE").getBlob(),
attachment2 : DriveApp.getFileById("traCetRacErI3hplVnkFax").getBlob()
};
uploadFile(url,form);
}
function uploadFile(url,form) {
var options = {
method : "POST",
payload : form
};
var request = UrlFetchApp.getRequest(url,options); // (OPTIONAL) generate the request so you
console.info("Request payload: " + request.payload); // can examine it (useful for debugging)
var response = UrlFetchApp.fetch(url,options);
console.info("Response body: " + response.getContentText());
}