Webhooks API

Webhooks are used by organizations to keep other applications in sync with their 4me accounts. Scripts that are published on public web servers (i.e. servers that can be accessed from the internet) can be called by webhooks in near real-time to collect data from 4me and to use this data to update an external application (or to perform an update in 4me itself).

What are webhooks?

Webhooks are a way to tell 4me to call a script on a web server whenever a given event occurs in 4me. A script is typically developed to execute specific actions for an organization that uses 4me. The organization for which such a script was developed normally hosts it on one of its own web servers.

Some possible uses for webhooks include:

Registering webhooks

Webhooks can be created in an organization’s 4me account by going to SettingsWebhooks in the 4me application, or with the 4me Webhooks API.

Select the event that needs to be listened to from the select box, and enter the URI that needs to receive the call from the webhook.

Once a webhook has been registered, 4me issues an HTTP POST call to the URI specified in the webhook every time the selected event occurs.

Webhooks are asynchronous events. They usually get sent quickly, but sometimes there can be a small delay.

4me calls the specified URI every time the event happens. This means that if, for example, a bulk-upload of 1,000 tasks is performed, the webhook is called 1,000 times if its selected event is task.create.

Webhook contents

Webhooks are sent as HTTP POST calls to URIs with a form-encoded body (with Content-Type: application/x-www-form-urlencoded) for easy parsing in almost any programming language. All webhook callback bodies contain the following parameters:

webhook_id
The ID of the webhook registered in 4me.
account
The url of your organization’s 4me account.
name
The name of the webhook that got fired.
event
The event of the webhook that got fired.
object_id
The id of the 4me record that triggered the event. Use this id to make a 4me API call to retrieve further details if needed.
person_id
The id of the person that triggered the event. This field is empty if the trigger was caused by a system event.
person_name
The name of the person that triggered the event. This field is empty if the trigger was caused by a system event.
payload[audit_line_id]
The ID of the audit entry that 4me generated for the creation or update of the 4me record that triggered the event. This parameter is included only when the event caused an audit entry to be generated. The addition of a note, or specifying a value in the Time spent field, does not cause an audit entry to get generated.
payload[source]
The source of the 4me record that triggered the event. This parameter is not included if the Source field of the 4me record that triggered the event does not contain a value.
payload[sourceID]
The source ID of the 4me record that triggered the event. This parameter is not included if the Source ID field of the 4me record that triggered the event does not contain a value.
payload[status]
The status of the 4me record that triggered the event. This parameter is not included if the 4me record that triggered the event does not have a Status field.
payload[previous_status]
The status of the 4me record before its status was updated, causing the event to be triggered. This parameter is included only for the events request.status-changed, problem.status-changed, change.status-changed, task.status-changed, project.status-changed and project_task.status-changed.
payload[team][id]
The ID of the team that is linked to the 4me record that triggered the event. This parameter is not included if the type of 4me record that triggered the event cannot be assigned to a team.
payload[team][name]
The name of the team that is linked to the 4me record that triggered the event. This parameter is not included if the type of 4me record that triggered the event cannot be assigned to a team.
payload[team][sourceID]
The source ID of the team that is linked to the 4me record that triggered the event. This parameter is not included if the Source ID field of this team does not contain a value.
payload[team][disabled]
The Disabled checkbox value of the team that is linked to the 4me record that triggered the event. This parameter is not included if this team is enabled.
payload[team][account][id]
The account ID of the team that is linked to the 4me record that triggered the event. This parameter is included whenever the 4me record that triggered the event can be assigned and has a value in its Team field.
payload[team][account][name]
The name of the account of the team that is linked to the 4me record that triggered the event. This parameter is included whenever the 4me record that triggered the event can be assigned and has a value in its Team field.
payload[member][id]
The ID of the person who is selected in the Member field of the 4me record that triggered the event. This parameter is included whenever the 4me record that triggered the event has a value in its Member field.
payload[member][name]
The name of the person who is selected in the Member field of the 4me record that triggered the event. This parameter is included whenever the 4me record that triggered the event has a value in its Member field.
payload[member][sourceID]
The source ID of the person who is selected in the Member field of the 4me record that triggered the event. This parameter is not included if the Source ID field of this person does not contain a value.
payload[member][disabled]
The Disabled checkbox value of the person who is selected in the Member field of the 4me record that triggered the event. This parameter is not included if this person is enabled.
payload[member][account][id]
The account ID of the person who is selected in the Member field of the 4me record that triggered the event. This parameter is included whenever the 4me record that triggered the event has a value in its Member field.
payload[member][account][name]
The name of the account of the person who is selected in the Member field of the 4me record that triggered the event. This parameter is included whenever the 4me record that triggered the event has a value in its Member field.

In addition references to URIs of interest are provided via the Link http header. The following relation types might be received:

canonical
URI to the affected resource viewable via the 4me UI application.
resource
URI to the affected resource viewable via the 4me API.
related
URI to a related affected resource viewable via the 4me API. For example, if a note was added to a request, then this points to the note details. The link with relation type resource points to the request in this example.

Request

POST  /mywebhook HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Host: myserver.example.com
User-Agent: 4me/1.0 (https://developer.4me.com/v1/webhooks)
Link: <https://mycompany.4me.com/requests/219>; rel="canonical",
      <https://api.4me.com/v1/requests/219>; rel="resource",
      <https://api.4me.com/v1/requests/219/notes/3455>; rel="related"
webhook_id=945&account=https%3A%2F%2Fmycompany%2E4me%2Ecom&event=request%2Enote-added&object_id=219&person_id=8543&person_name=Howard%20Tanner&payload[source]=self%20service&payload[status]=change_pending&payload[team][id]=372&payload[team][name]=End-User%20Support%2C%20Houston&payload[member][id]=8529&payload[member][name]=Tom%20Edwards

which in JSON format equals:

{
  "webhook_id": 945,
  "account": "https://mycompany.itrp.com",
  "name": "Note added to Request of mycompany",
  "event": "request.note-added",
  "object_id": 219,
  "person_id": 8543,
  "person_name": "Howard Tanner",
  "payload[source]": "self service",
  "payload[member][name]": "Tom Edwards",
  "payload[member][id]": 8529,
  "payload[status]": "change_pending",
  "payload[team][id]": 372,
  "payload[team][name]": "End-User Support, Houston"
}

Expected response

Make sure the status code of the response is between 200 and 299 ( preferably 200 OK ) which indicates success. 4me simply discards the rest of the results of the webhook.

The webhook request to the web server on which the script is hosted times out after 10 seconds. Move long running processes (e.g. PDF generation) into an asynchronous background task and make sure that the script responds immediately to the 4me server.

Failed callbacks

If 4me receives anything other than a 2xx HTTP response code (likely meaning that the script has been moved, is non-existent, or misconfigured) or requests to the web server times out, 4me retries the HTTP POST call periodically. After several failures, the message is dropped and no further delivery attempts are made. Email notifications are sent out for these exceptions. By default, these exception notifications are sent to each person who has the Account Administrator role of the account in which the webhook is registered.

Automatic disabling

A webhook can be temporarily disabled. In addition, 4me automatically disables a webhook when there have been 20 consecutive failures for the exact same webhook over a period longer than a week. The details of the disabled webhook (viewable via the 4me application or via the Webhooks API) then include the last error as received by 4me.

Security considerations

As 4me needs to call a script on a server of a different organization via a publicly available connection, there is a potential vulnerability to hacks on that server.

Verifying a webhook

The easiest way to verify a webhook is by adding a secret query parameter to the URI, and then to have this parameter checked by the script.

In addition, it is possible to use basic http authentication by providing the username:password in the webhook URI, like this:

https://myuser:mylogin@myserver.example.com/mywebhook

Lastly, 4me always sends the ID of the webhook as part of the response, which could also be used in a verification step. The ID of a webhook can be found by selecting the webhook in the 4me application and looking at the number that is visible in the address bar of the browser, and/or via the 4me Webhooks API.

Testing webhooks

Testing that a webhook works can be a bit of a challenge. Thanks to Webhook Tester this has been made quite simple.

Setting up

First, be sure to have the Account Administrator role within the 4me application.

Then visit Webhook Tester and copy the URL that is then shown in the header.

Within the 4me application go to SettingsWebhooks. Add a new Webhook, select for example “request.update” from the Event select box, and paste the URL from Webhook Tester into the URI field, and save this webhook.

Testing the webhook is fired

To test that the webhook fires simply create a request and update it. Check if the webhook fired by refreshing the page at Webhook Tester.

Example conversation

In the example below a webhook test is performed using the command line tool curl. This example shows how to test a webhook without actually modifying any data in 4me.

Setting up

First create a webhook:

$ curl -v -u "API-TOKEN:x" -X POST \
       -d '{"event":"request.create", "uri":"http://requestb.in/nlety4nl"}' \
       https://api.4me.com/v1/webhooks/
Status: 201 Created
Location: https://wdc.4me.com/webhooks/2
{
  "id": 2,
  "name": "Request created in WDC",
  "event": "request.create",
  "uri": "http://requestb.in/nlety4nl",
  "created_at": "2016-01-27T20:38:32-06:00",
  "updated_at": "2016-01-27T20:38:32-06:00"
}

Then test the webhook that was just created:

$ curl -v -u "API-TOKEN:x" -X POST https://api.4me.com/v1/webhooks/2/test
Status: 204 No Content

Refresh the RequestBin page. It should show a new event posted for the event request.create that triggered the webhook with webhook_id 2.