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.