Webhooks

Webhooks allow you to build or setup integrations with Teamwork products by subscribing to certain events. These events could be triggered by the actions of anyone in the organization. When one of these events is triggered, we'll send a HTTP POST request to an URL you provide. Webhooks could be used to build integrations, generate reports, back up data, and more.

You can create a webhook through the web interface by going to your user Settings and selecting Webhooks in the sidebar. Or you could create a webhook through the API, see Webhooks API documentation. The Webhooks feature can only be accessed if you are a site admin on Spaces.

When configuring a webhook you'll be able to choose which events you'd like to be notified for. By subscribing to only the events in which you're interested in you can reduce the amount of HTTP requests to your server. You can add or remove events from your webhook without recreating it via the UI or API.

Below is a list of the available Spaces events:

access.created : Access request

createdcomment.created : Comment created

comment.updated : Comment updated

comment.deleted :Comment deleted

page.created : Page created

page.updated : Page updated

page.deleted : Page deleted

page.updated.reverted : Page updated via a version revert

page.updated.moved : Page moved

pagepermission.created : Page permission createdpage

permission.deleted : Page permission deleted

pagepermissions.deleted : Page permissions deleted

pluginstate.created : Plugin state created

pluginstate.updated : Plugin state updated

space.deleted : Space deleted

space.imported : Space imported via confluence importer

user.updated : User updated

user.enabled : User enabled

user.disabled : User disabled

page.updated.trashed : Page trashed

page.updated.restored : Page restored from trash

Unless stated otherwise the request body will contain the same JSON model as described in our API documentations for the type. For example, the access.created event will contain the access model.

Configuring your server to receive a new webhook is no different from creating any page on your website. With PHP, you might create a new .php file on your server; with a framework like Sinatra, you would add a new route with the desired URL. Remember, with webhooks, your server is the server receiving the request.

When an event is triggered in your Teamwork Spaces installation a HTTP POST request is sent to the registered Webhook URL.

Event : The event name of the webhook e.g access.created

Signature : The signature generated by Teamwork using your secret token so you can determine if the request is genuine

Delivery : The delivery UUID, this is listed in the UI for you to cross reference in your webhook settings

User-Agent : The version of the webhook service used to make this request

You can optionally provide a token when creating the webhook, this token will be used as the key in the HAMC-SHA256 hex encoded signature in the Signature header. Code samples are given below as an example of how to verify this signature by creating your own and comparing it.

Responses with a status code greater than 400 will be considered a failure, failures will be reattempted 3 times before permanent failure.

Reattempts are made on the following schedule:

1st re-attempt : 1 minute delay

2nd re-attempt : 5 minute delay

3rd re-attempt : 10 minute delay

Go

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

const secretToken = "bea1ffc4e56d5056b6e0c7ee24b47b5"

// some error handling omitted for brevity
func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			http.Error(w, "", http.StatusBadRequest)
			return
		}

		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "", http.StatusInternalServerError)
			return
		}

		if validSignature(r.Header.Get("Signature"), b) {
			http.Error(w, "", http.StatusBadRequest)
			return
		}

		fmt.Println("Verified Teamwork Spaces Webhook:")
		for h, v := range r.Header {
			fmt.Printf("%s: %s\n", h, v[0])
		}
		fmt.Println("-------------------")
		fmt.Println(string(b))
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}

func validSignature(sig string, body []byte) bool {
	mac := hmac.New(sha256.New, []byte(secretToken))
	mac.Write(body)
	return sig == hex.EncodeToString(mac.Sum(nil))
}