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.

To create a webhook

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.

Events

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:

Name Cause
access.created Access request created
comment.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 created
pagepermission.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

Request body

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.

Receiving and handling

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.

Delivery headers

Header Description
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

Verifying the 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.

Responding to the request

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:

Reattempt # Delay (mins)
1 1
2 5
3 10

Code samples

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))
}

PHP

<?php
define('SECERT_TOKEN', 'bea1ffc4e56d5056b6e0c7ee24b47b5');
$body = file_get_contents('php://input');

$sig = hash_hmac('sha256', $body, SECERT_TOKEN, false);
if ($sig !== $_SERVER['SIGNATURE']) {
  error_log('Bad signature');
  http_response_code( 400);
  exit;
}

error_log('Verified Teamwork Spaces Webhook Event: '.$_SERVER['EVENT']);

error_log($body);

Feedback

If you have any feedback or suggestions, feel free to contact us at api@teamwork.com.