Webhooks (beta)

This page documents the Webhooks functionality available in CA Agile Central.

Webhooks are an advanced tool that can be used to integrate your CA Agile Central subscription with other, external services. A webhook makes an HTTP POST request to a URL of your choosing whenever certain types of changes occur in your Agile Central subscription.

What is a webhook?

Webhooks are "user-defined HTTP callbacks".  They are usually triggered by some event,
such as pushing code to a repository or a comment being posted to a blog. When that
event occurs, the source site makes an HTTP request to the URI configured for the webhook.
Users can configure them to cause events on one site to invoke behaviour on another.

SendGrid: What's a Webhook?

Target Audience

The target audience for this document is developers who want to efficiently extract information about changes happening in their CA Agile Central data.

You should be familiar with HTTP, JSON, and CA Agile Central's Web Services API.

Overview

Every change in CA Agile Central results in a Change Message, which can potentially be matched by a webhook. Examples of changes include:

  • Creating a User Story
  • Changing the name of a defect
  • Moving a User Story to "In Progress"
  • Adding a Discussion entry to a Story
  • Moving a Story into a Feature
  • Closing a Defect
  • etc.

Essentially any change at all results in a message that can be matched to a webhook.

A webhook includes a set of conditions that determine which change messages it will match. Conditions are very flexible and allow you to decide very precisely which changes you want to be notified about.

When a webhook matches a change message, a JSON payload containing information about precisely what changed will be posted to the webhook's Target URL.


Webhooks API

Webhooks are created and managed through an HTTP API.

The base URL for this API is: https://rally1.rallydev.com/apps/pigeon/api/v2

Briefly, the following actions are available. Each will be described in more detail below.

HTTP Verb Path Action
POST /webhook Create a new webhook
GET /webhook Return a paged result set listing webhooks in your subscription
GET /webhook/{id} Return a specific webhook by its id
PATCH /webhook/{id} Update a webhook
DELETE /webhook/{id} Delete a webhook

Authentication

All requests to the webhooks API must be authenticated, proving that the request comes from a user with a valid and active CA Agile Central account. There are several ways you can authenticate.

First, you need a valid key, which can be:

  1. An API Key, which you can create here.
  2. The ZSESSIONID cookie returned when you log in to CA Agile Central.

Then, when making requests to the webhooks API, pass the key as the ZSESSIONID cookie. The method for doing this will depend on the programming language and HTTP library that you use. Here is an example of a raw HTTP request, showing how to authenticate.

GET /apps/pigeon/api/v2/webhook HTTP/1.1
Host: rally1.rallydev.com
Cookie: ZSESSIONID={my-key}

Note that the webhooks API does not currently support HTTP Basic authentication, so you cannot simply pass a username and password directly.

If you do not authenticate properly, a response status of 401 (Unauthorized) will be return from all requests.

Example: Creating a Webhook

You create a webhook by making a POST request to /apps/pigeon/api/v2/webhook with a JSON body.

Here is an example:

{
  "AppName":     "My Cool App",
  "AppUrl":      "www.example.com",
  "Name":        "My first webhook",
  "TargetUrl":   "http://my.service.com/ready/to/receive/post",
  "ObjectTypes": ["HierarchicalRequirement", "Defect"]
  "Expressions": [
    {
      "AttributeName": "PlanEstimate",
      "Operator":      ">",
      "Value":         10
    },
    {
      "AttributeName": "Workspace",
      "Operator":      "=",
      "Value":         "31685839-67fe-4f9d-9470-da56c222998b"
    }
  ]
}

The response should have an HTTP status of 200 and a body that looks something like this:

{
  "AppName": "My Cool App",
  "AppUrl": "www.example.com",
  "Name": "My first webhook",
  "TargetUrl": "http://my.service.com/ready/to/receive/post",
  "ObjectTypes": ["HierarchicalRequirement", "Defect"],
  "Expressions": [
    {
      "AttributeID": null,
      "AttributeName": "PlanEstimate",
      "Operator": ">",
      "Value": 10
    },
    {
      "AttributeID": null
      "AttributeName": "Workspace",
      "Operator": "=",
      "Value": "31685839-67fe-4f9d-9470-da56c222998b"
    }
  ],

  "CreatedBy": null,
  "CreationDate": "2016-06-23T20:54:26.978Z",
  "Disabled": false,
  "Security": null,
  "LastUpdateDate": "2016-06-23T20:54:26.978Z",
  "ObjectUUID": "51c8d135-a8ce-4bd2-9865-e3ec045cb47e",
  "OwnerID": "4481dd2f-2810-4fb9-bd85-bb81daf2510c",
  "SubscriptionID": 100,
  "_objectVersion": 1,
  "_ref": "https://rally1.rallydev.com/apps/pigeon/api/v2/webhook/51c8d135-a8ce-4bd2-9865-e3ec045cb47e",
  "_type": "webhook",
}

Webhook Attributes

At its core, a webhook consists of two things:

  1. A TargetUrl, where the webhook's payload will be POSTed.
  2. An array of one or more Expressions, which determine the conditions under which the webhook will fire. (Expressions will be explained in detail below.)

In addition to these, there are a few more attributes that are required.

The following are all the attributes that you can have in a webhook:

Name type required? Description
AppName String yes Informational. Describes the external app or integration.
AppUrl String yes Informational. A URL where more information about the app can be found.
Name String yes Informational. To help you identify different webhooks that you have created.
TargetUrl String (URL) yes The URL of a web service that can receive an HTTP POST request containing the payload.
Expressions Array[Expression] yes Array of one or more Expression maps. See below for details.
ObjectTypes Array[String] no Optional array of object types that the webhook can match.
Security String (40 chars max) no If provided (via create or update), the value of this field will be present in the Header of the request posted to the TargetUrl (can be used to validate that the request sender is authorized). WARNING: This value should not contain any double quote (") characters.
Disabled Boolean no If set to true, the webhook will not fire until re-enabled.
OwnerID String (UUID) no UUID. The ObjectUUID of a CA Agile Central User who will own the webhook.
CreatedBy String no Informational. Intended to identify the tool (if any) used to create the webhook.

The following are additional read-only status fields that are updated by the system upon webhook firing:

Name type Description
FireCount Number Total number of times this webhook has been fired (includes both successful and unsuccessful attempts
ErrorCount Number Number of consecutive webhook firing failures, is reset to 0 upon successful firing
LastStatus Number HTTP status code returned by last firing of the webhook
LastWebhookResponseTime Integer Elapsed time of webhook fire and receiving the response, success code is 200
LastSuccess Timestamp Updated when firing the webhook results in a 200 level status code
LastFailure Timestamp Updated when firing the webhook fails, times-out or results in a non 200 level status code

Note that the above fields will only have non-null values if the webhook has never been fired.

Expressions

A webhook contains an array of Expressions.

Generally, an Expression consists of an Attribute (specified by either ID or Name), an Operator, and a Value. When any object is changed, the value of the attribute on the changed object is compared, using the given operator, to the Value field of the Expression.

For example, consider the following Expression:

{
  "AttributeName": "ScheduleState",
  "Operator": "=",
  "Value": "In-Progress"
}

This will match whenever there is a change to any object that has a ScheduleState attribute with a value of "In-Progress". Note that the ScheduleState attribute itself need not be changed - any change to an object in that state will match.

Currently, expressions are always combined with logical and; that is, all Expressions in a webhook must match the change message in order for the webhook as a whole to match.

You can also specify Attributes by ID, instead of Name. For example, the following Expression is (almost) equivalent to the one show above:

{
  "AttributeID": "aad205e0-2fbe-11e4-8c21-0800200c9a66",
  "Operator": "=",
  "Value": "In-Progress"
}

Because "aad205e0-2fbe-11e4-8c21-0800200c9a66" is the ObjectUUID of the AttributeDefinition for ScheduleState, this Expression will also match on changes to any object with a ScheduleState of "In-Progress".

There are a couple of differences when using an Attribute's ID instead of its Name. The ID is guaranteed to match using only the intended Attribute, whereas the Name is less precise. If, for example, a new Attribute named "ScheduleState" were added to another object in the model, your webhook could begin to match changes that you did not intend.

Also, using AttributeID guarantees that your expressions will continue to work correctly even if an attribute get renamed, as can happen for custom attributes.

You can find Attribute IDs by examining TypeDefinitions and AttributeDefinitions defined in the WSAPI Object Model.

For example, the AttributeID for ScheduleState, used above, was discovered by looking at https://rally1.rallydev.com/slm/webservice/v2.0/TypeDefinition/-51084/Attributes and copying the ObjectUUID property of the "ScheduleState" attribute. (Note: make sure you are logged in to Agile Central).

ObjectTypes

The ObjectTypes field in a webhook is an optional way to limit the types of objects that can match. You can think of it as a shorthand for an Expression.

For example, this:

{ ...
  "ObjectTypes": ["Defect", "TestCase"]
}

is essentially equivalent to this:

{ ...
  "Expressions": [
    {
      "AttributeName": "ObjectType",
      "Operator": "~", // "equals-one-of" operator
      "Value": ["Defect", "TestCase"]
    }
}

except that there is not actually an attribute named "ObjectType".

If ObjectTypes is omitted or empty, it means "any type" - that is, the webhook can match changes to objects of any type, as long as its Expressions match.

Note: WSAPI typically refers to Portfolio Items like so: PortfolioItem/<Name> (e.g. PortfolioItem/Feature, PortfolioItem/Initiative or PortfolioItem/MyCustomPortfolioItemName). To scope webhooks to Portfolio Items, omit the PortfolioItem/ and use the name only (e.g. Feature, Initiative or MyCustomPortfolioItemName) in the ObjectTypes array.

Example webhook to get changes to Feature PortfolioItems for a specific project:

{
  "Expressions": [
    {
      "AttributeName" : "Project",
      "Operator": "=",
      "Value": "2b286e96-5ef8-443c-b630-dc2cdbf6fcd8"
    }
  ],
  "ObjectTypes": ["Feature"],
  "TargetUrl": "http://somecool.url/webhook",
  "Name": "Feature changes in Project X",
  "AppName": "KG",
  "AppUrl": "www.thebomb.com"
     
}

Operators

The required fields in an Expression depend on the Operator.

The following operators require both a Value and exactly one of AttributeID or AttributeName.

Operator Description
= Equal
!= Not equal
< Less than
<= Less than or equal
> Greater than
>= Greater than or equal
changed-to Value changed to
changed-from Value changed from

The following operators require an AttributeID or AttributeName, and a Value that is an Array of individual values.

Operator Description
~ "Equals one of". Matches when the object's value for the attribute is equal to one of the values given in the Expression
!~ "Equals none of". Matches when the object's value for the attribute is not equal to any of the values given in the Expression

The following operators require only an AttributeID or AttributeName (no Value)

Operator Description
has The object has some (non-null) value for the attribute
!has The object does not have the attribute, or its value is null
changed The value of the attribute was changed on the object

Webhooks API Methods

Create

Create a webhook by POSTing to the /api/v2/webhook endpoint, as detailed above, in the section "Example: Creating a Webhook".

Note that the response will contain a _ref field containing the direct URL of the created webhook, which can be used to get, update, or delete it.

Get

Get an individual webhook by making an HTTP GET request to its _ref URL.

Delete

Delete a webhook by making an HTTP DELETE request to its _ref URL.

Update

Update a webhook by making an HTTP PATCH request to its _ref URL.

Only fields sent in the body of the PATCH request will be updated. Omitted fields will retain their old value. This means that you can, for example, send an update containg only this:

{"Disabled": true}

to disable a webhook.

Note that Array-valued fields, including ObjectTypes and Expressions, cannot be partially updated - they can only be replaced. In other words, if you want, for example, to add a single Expression to an existing webhook, you must send in an Expressions array containing all existing expressions, in addition to the the new expression. Any expressions not sent will be deleted.

Query

Query for webhooks in your subscription by making an HTTP GET request to /api/v2/webhook, optionally with query parameters.

Accepted Query Parameters
Parameter Description Default Example(s)
order The field name to sort by and an optional direction, separated by a space. ascending (asc) order=AppName desc, or order=Disabled desc,Name
pagesize The number of results to return per page 20, max 200 pagesize=100
start The start index for the query 1 start=101

Example query:

To get 200 results, second page of results, ordered by Name descending:

GET /api/v2/webhook?pagesize=200&start=201&order=Name desc

Response:

{
  "Results": [ /// 200 items
    {
      "LastUpdateDate": "2015-09-10T14:31:52.460Z",
      "Expressions": [/*...*/],
      "SubscriptionID": 123,
      "_ref": "https://rally1.....",
      "_type": "webhook",
      "OwnerID": "cc1fcb3f-7f12-4abe-b1fc-123cd2123123",
      "TargetUrl": "https://foo.bar/blah",
      "_objectVersion": 1,
      "Disabled": false,
      "Name": "Zze Best Webhook", //sort by name descending
      "AppName": "Best App!",
      "ObjectUUID": "123ba678-12e4-1ae4-b234-12f4bbb8901c"
      "CreationDate": "2015-09-10T14:31:52.460Z",
      "AppUrl": "https://best.app"
    },
    {/*...*/},
    /*...and so on*/
  ],
  "TotalResultCount": 467,
  "PageSize": 200,
  "StartIndex": 201 // second page of results
}

100 results, first page of results, ordered by Name ascending


Payload

Upon firing, a webhook will POST a JSON payload to its TargetUrl. The content of this payload is as follows:

{
  "rule": { ... Webhook that matched ... },
  "message": { ... Change Message (see below) ... }
}

The "rule" is the Webhook that fired, in the same format as is returned by the webhooks HTTP API.

The "message" is the Change Message that matched. It describes an object in the Agile Central object model that changed, what changes were made, when, and who changed it.

Here is an example of a Change Message:

{
  "message_id": "6750d9fe-c289-478f-b188-39d483c9ea39",
  "message_version": 2,
  "subscription_id": 209,
  "action": "Updated",
  "object_id": "6ab3d609-8011-47f8-2372-4d20ec313c20",
  "object_type": "HierarchicalRequirement",
  "ref": "https://rally1.rallydev.com/slm/webservice/v2.x/hierarchicalrequirement/6ab3d609-8011-47f8-2372-4d20ec313c20",
  "detail_link": "https://rally1.rallydev.com/slm/#/detail/userstory/52790947441",
  "project": {
    "uuid": "749a8fd7-dfa9-897b-bec7-d86e01aa8b3a",
    "name": "My Project"
  },
  "transaction": {
    "timestamp": 1466627088236,
    "trace_id": "c38ee7f5-543d-4214-bf02-55a180227826",
    "user": {
      "uuid": "1d1c15d7-9de6-4df8-aaf1-b9adb4edfb56",
      "username": "some.user@example.com",
      "email": "some.user@example.com"
    }
  },
  "changes": {... (see below) ...},
  "state": {... (see below) ...},
}

It contains these fields:

Name Type Description
message_id UUID An id unique to this change message
message_version Integer Currently always 2
subscription_id Integer SubscriptionID of your Agile Central subscription
action String One of "Created", "Updated", "Recycled"
object_id UUID ObjectUUID of the changed object
object_type String Type of the changed object. eg: "Defect", "HierarchicalRequirement", "Project", etc.
ref URL Link to the changed object in WSAPI
detail_link URL If applicable to the type, a link to the HTML page where the object can be viewed or edited
project Object If applicable to the type, contains the uuid and name of the containing project.
transaction Object Contains the user who made the change, and the timestamp when it happened.
state Object See below
changes Object See below

The state map contains the entire state of the object after the change. It takes the form of a map where the keys are AttributeUUIDs, and the values are maps containing the attribute's "value", "type", "name", "display_name", and "ref".

Here is an example of the state map from a change message:

"state": {
    "500a0d67-9c48-4145-920c-821033e4a832": {
      "value": "My Cool Story",
      "type": "String",
      "name": "Name",
      "display_name": "Name",
      "ref": null
    },
    "ae8ecc9f-b9a0-42a4-a6e3-c83d7f8a7070": {
      "value": {
        "name": "0",
        "ref": "https://rally1.rallydev.com/slm/webservice/v2.x/project/749a8fd7-dfa9-421b-bec7-d86e01aa8b3a",
        "detail_link": "https://rally1.rallydev.com/slm/#/detail/project/18951302164",
        "id": "749a8fd7-dfa9-421b-bec7-d86e01aa8b3a",
        "object_type": "Project"
      },
      "type": "Project",
      "name": "Project",
      "display_name": "Project",
      "ref": null
    },
    ... etc ...
}

The changes map is similar in shape to the state map, but it contains only attributes that were changed. The map representing each attribute contains, in addition to the value as it appears in the state section, fields called old_value, added, and removed.

Example:

"changes": {
  "f5b1fb22-6c15-44b5-a592-19189fafe5f2": {
    "value": 8,
    "old_value": 7,
    "added": null,
    "removed": null,
    "type": "Integer",
    "name": "VersionId",
    "display_name": "VersionId",
    "ref": null
  },
  "aad205e0-2fbe-11e4-8c21-0800200c9a66": {
    "value": {
      "name": "Defined",
      "ref": "https://rally1.rallydev.com/slm/webservice/v2.x//c7ce12e8-bb4f-457d-af13-accd7868edb1",
      "detail_link": null,
      "id": "c7ce12e8-bb4f-457d-af13-accd7868edb1",
      "order_index": 1,
      "object_type": "State"
    },
    "old_value": {
      "name": "In-review",
      "ref": "https://rally1.rallydev.com/slm/webservice/v2.x//0955ce77-41a8-44cc-ba87-9de63749098e",
      "detail_link": null,
      "id": "0955ce77-41a8-44cc-ba87-9de63749098e",
      "order_index": 0,
      "object_type": "State"
    },
    "added": null,
    "removed": null,
    "type": "State",
    "name": "ScheduleState",
    "display_name": "Schedule State",
    "ref": null
  }
}

For all attribute types other than Collection, the old_value field will be populated, and the added and removed fields will be null.

For attributes of type Collection, the old_value field will be null, the added field will contain an array of child objects that were added to the collection, and removed will contain an array of child objects that were removed from the collection.

Automatic Deletion on 410

If a webhook fires and receives an HTTP response status of 410 (Gone) from the target, the currently-firing webhook will be immediately deleted, and will not fire again.

You can use this feature as a mechanism to clean up unwanted webhooks, by making your service return 410 (rather than, say, 404) when it is no longer needed.

Latency

There are currently no specific latency guarantees.

Typically, webhooks will fire about 1 to 2 seconds after a change is made in CA Agile Central.

Retries

There is currently no retry mechanism. If your webhook endpoint is not able to accept the POST request (eg: your server is temporarily down, there is a network outage, etc.), no further attempts will be made to POST that payload.

How to find an Attribute UUID

You can find an Attribute UUID by querying the type definition endpoint in Rally.

  1. Log in to Rally, and navigate to this url: https://rally1.rallydev.com/slm/webservice/v2.0/TypeDefinition?fetch=Attributes
  2. Find the object type that you are interested in by looking at the _refObjectName. (Note that the User Story type is called "HierarchicalRequirement")
  3. Navigate to the url specified in the _ref field on the Attributes.
  4. Find the entry for the attribute for which you would like to create a webhook.
  5. Use the ElementName or ObjectUUID field of the attribute, depending on whether you are creating an Expression by AttributeName or AttributeID.

Security

When a payload is POSTed to your server, how can you be sure that the request came from Agile Central, and not from an attacker?

There are some simple practices that you can use to make your webhooks as secure as possible.

  1. Use HTTPS. Simply set the targetUrl field of your webhooks to an https://... endpoint, rather than http://..., and we will call you over a secure, TLS-encrypted connection. Make sure that your server supports incoming HTTPS requests.
  2. Include an un-guessable component in your targetUrl's path. For example, something like https://your.domain.com/webhooks/ca95aaab-836f-42b9-84fc-265f250755f4.
  3. Have your webhook processing server validate that the unique portion of your URL is one that you created.

By following these steps, you can minimize the probability that an outside attacker will be able to successfully spoof a request to your webhook server. The unique identifier in your URL can be neither guessed nor intercepted, and cannot be discovered without access to your Agile Central subscription's webhooks data.

Usage Considerations

Rally reserves the right to suspend or disable webhooks based on non-usage or misuse.