Webhook processing with Google Apps Script

== Habitica-side ==
==== Third party tools ====
There are third-party tools to set up webhooks for your Habitica account.
* Habitica Webhook Editor
*Habitica Api-Helper Tool
==== Webhook categories ====
With the help of those tools you can set up Habitica to listen to certain, predefined actions:
Webhook Category 	Action
quest activity 	
* quest started
* quest finished
* quest invited
task activity 	
* task created
* task updated
* task deleted
* task checked
* checklist item checked
user activity 	
* pet hatched
* mount raised
* leveled up
group chat received	
*group chat received from a specific group (party/guild)
==== Official Habitica API-Documentation ====
Detailed information is provided by the official Habitica API-Documentation.
==== Example data being transferred by the webhook ====
Example-data transmitted to the Web-App (retrieved by var dataContents = e.postData.contents given function doPost(e)):
Quest invitation (including questOwner as the person starting the quest):
{"type":"questInvited","group":{"id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","name":"My Habitica Party"},quest":{"key":"owl","questOwner":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}","webhookType":"questActivity","user":{"_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"<nowiki>}}</nowiki>
Quest finished:  
{"type":"questFinished","group":{"id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","name":"My Habitica Party"},"quest":{"key":"butterfly"},"webhookType":"questActivity","user":{"_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}}
 Task created:
{"type":"created","task":{"challenge":{},"group":{"approval":{"required":false,"approved":false,"requested":false},"assignedUsers":[],"sharedCompletion":"singleCompletion"},"completed":false,"collapseChecklist":false,"type":"todo","notes":"","tags":[],"value":0,"priority":1,"attribute":"str","byHabitica":false,"text":"Add To Do","_id":"0ec3a680-3c8b-4e61-88f1-2848782226e9","reminders":[],"checklist":[],"createdAt":"2020-03-30T10:23:10.956Z","updatedAt":"2020-03-30T10:23:10.956Z","userId":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","id":"0ec3a680-3c8b-4e61-88f1-2848782226e9"},"webhookType":"taskActivity","user":{"_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}}
Task scored:
{"type":"scored","direction":"up","delta":1,"task":{"challenge":{},"group":{"approval":{"required":false,"approved":false,"requested":false},"assignedUsers":[],"sharedCompletion":"singleCompletion"},"completed":true,"collapseChecklist":false,"type":"todo","notes":"","tags":[],"value":1,"priority":1,"attribute":"str","byHabitica":false,"checklist":[],"reminders":[],"createdAt":"2020-03-30T10:23:10.956Z","updatedAt":"2020-03-30T10:27:28.236Z","_id":"0ec3a680-3c8b-4e61-88f1-2848782226e9","text":"Add To Do","userId":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","dateCompleted":"2020-03-30T10:27:28.204Z","id":"0ec3a680-3c8b-4e61-88f1-2848782226e9"},"user":{"_tmp":{"quest":{"progressDelta":1.475,"collection":1},"drop":{"target":"Desert","canDrop":true,"value":1,"key":"Potatoe","type":"Food","dialog":"You've found a Potato!"}},"stats":{"buffs":{"str":0,"int":0,"per":0,"con":0,"stealth":0,"streaks":false,"snowball":false,"spookySparkles":false,"shinySeed":false,"seafoam":false},"training":{"int":0,"per":0,"str":0,"con":0},"hp":46.35717357116706,"mp":136.94362288135568,"exp":199218.27891340942,"gp":3319.1888075206516,"lvl":931,"class":"wizard","points":0,"str":0,"con":0,"int":100,"per":0,"toNextLevel":226140,"maxHealth":50,"maxMP":527},"_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"},"webhookType":"taskActivity"}
Task deleted:
{"parameter":{},"contextPath":"","contentLength":672,"queryString":"","parameters":{},"postData":{"type":"application/json","length":672,"contents":"{\"type\":\"deleted\",\"task\":{\"challenge\":{},\"group\":{\"approval\":{\"required\":false,\"approved\":false,\"requested\":false},\"assignedUsers\":[],\"sharedCompletion\":\"singleCompletion\"},\"completed\":false,\"collapseChecklist\":false,\"type\":\"todo\",\"notes\":\"\",\"tags\":[],\"value\":-33.71057310093657,\"priority\":1,\"attribute\":\"str\",\"byHabitica\":false,\"checklist\":[],\"reminders\":[],\"createdAt\":\"2020-02-09T10:46:46.723Z\",\"updatedAt\":\"2020-03-30T10:22:43.497Z\",\"_id\":\"dc5d0acd-e889-4c42-850f-9110aef8be33\",\"text\":\"test\",\"userId\":\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\",\"id\":\"dc5d0acd-e889-4c42-850f-9110aef8be33\"},\"webhookType\":\"taskActivity\",\"user\":{\"_id\":\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"}}","name":"postData"}}
You can see that e.postData.contents may include backslashes. Please use var dataContents = JSON.parse(e.postData.contents) to parse the string.
Requirement for the Webhook to be processed is a Server that listens to the webhook. So before setting up the Habitica-webhook, please set up the server side:
== Server-side (backend) ==
==== Google Apps Server Script (Web-App) ====
On execution of one of the certain, predefined webhook-actions, Habitica posts a request to a server, which processes the request. This server can be reached via a URL. The server will listen to POST requests from Habitica. E.g. in Google Apps Script, please use a doPost(e) function like in the example below.
Google Apps can be used to set up such a server by deploying the server-script as a Web App:
https://developers.google.com/apps-script/guides/web
==== Example Auto-Accept Script ====
Here is an example of a server-script that will instantly auto-accept a quest invitation:
 // [Users] Required script data to fill in
 const USER_ID = "PasteYourUserIdHere";
 const API_TOKEN = "PasteYourApiTokenHere"; // Do not share this to anyone
 
 // [Users] Do not edit code below this line
 
 // [Developers] If you're authoring your own script, place your user ID and
 // script name here. If just using this as a sample, you can leave as is.
 const AUTHOR_ID = "01daa187-ff5e-46aa-ac3f-d4c529a8c012";
 const SCRIPT_NAME = "Faster Auto Accept Quests";
 const HEADERS = {
 "x-client" : AUTHOR_ID + "-" + SCRIPT_NAME,
 "x-api-user" : USER_ID,
 "x-api-key" : API_TOKEN,
 }
 
 // [Developers] This is the function that will be executed whenever Habitica
 // encounters the event designated by the webhook
 function doPost(e) {
 var dataContents = JSON.parse(e.postData.contents);
 var type = dataContents.type;
 
 if (type == "questInvited") {
 api_acceptQuest();
 }
 
 return HtmlService.createHtmlOutput();
 }
 
 function api_acceptQuest() {
 var params = {
 "method" : "post", 
 "headers" : HEADERS,
 "muteHttpExceptions" : true,
 }
 
 var url = "https://habitica.com/api/v3/groups/party/quests/accept";
 UrlFetchApp.fetch(url, params);
 }
More examples here: (including possibility to display data on google sheets)
https://github.com/PitiTheGrey/google-script-Webhook-Server-for-Habitica
==== Handling the Server-Script ====
Saving, managing versions and deploying the Server-Script as Web-App:
Server Script 	Explanation
Save the script 	Save the script using "Manage Versions". Every change to the code shall be saved as a new version.
Deploy as "Web App" 	Every new version has to be deployed as "Web App". Deploying the script as a web-app will deliver the Server-Url (which is needed to set up the Habitica-side webhook). Changes to code and versions will not alter any given Url. For changes to take effect, it is required to deploy every new version as a web app everytime the code/version changes.
Settings for the "Web App"	Deploying the script as a web-app will work for Habitica webhooks only if following settings are made:
- "Execute the script as 'Yourself'." ("Me" on google scrips)
- "Grant access to everyone, even anonymous"
URL of the Web-App 	On deploying the Web-App, Google shows the Web-App-URL. This is the Url you will need to set up the Habitica-side Webhook. (The Habitica-side Webhook does not require to be changed upon any new version of the server-side script!).
== Questions, troubleshooting ==
General questions can be asked in Habitica's Aspiring Comrades Guild.
For specific questions, regarding the Api-Helper-Tool or the "PitiTheGrey" example server script, please ask in the Habitican Wardrobes Guild.
Piti! March 30th, 2020
ja:Google Apps ScriptによるWebhook処理
zh:使用谷歌应用脚本处理Webhook
Category:Extensions, Add-Ons, and Customizations