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