Last updatedSep 27, 2019

Webhooks

A webhook is a user-defined callback over HTTPS. You can use Jira webhooks to notify your app or web application when certain events occur in Jira. For example, you might want to alert your remote application when an issue has been updated or when sprint has been started. Using a webhook to do this means that your remote application doesn't have to periodically poll Jira (via the REST APIs) to determine whether changes have occurred.

Overview

A webhook in Jira is defined by the following information, which you need to provide when registering (i.e. creating) a new webhook:

  • a name for the webhook created ("Webhook for my remote app")
  • the URL where the callback should be sent (only secure HTTPS URLs are allowed)
  • (optional) the scope of the webhook, for example either "all issues" or a limited set of issues specified by JQL ("project = TEST and fixVersion = future")
  • the events to post to the URL, either "all events" or a specific set of events

Registering a webhook

To register (i.e. create) a webhook, you can use any of the following methods:

Configuring a webhook

The information in this section will help you configure a webhook, whether you are creating a new one or modifying an existing one.

Available webhook events

The following events are available for Jira webhooks. The string in parentheses is the name of the webhookEvent in the response.

Restricting your events to a subset of issues via JQL

If you only want your webhook events to fire for a specific set of issues, you can specify a JQL query in your webhook to send only events triggered by matching issues. For example, you could register for all Issue Deleted events on issues in the Test ("TEST") Project with the Performance component.

JQL filtering is only supported by a subset of webhook events. Refer to the event sections below for details.

Note, although the JQL that uses standard Jira fields is very performant, some custom fields, because of their implementation, can take multiple seconds to query. You should take this into account when using JQL for a webhook, as the query will be run with each event.

Issue webhooks

  • created (jira:issue_created)
  • updated (jira:issue_updated)
  • deleted (jira:issue_deleted)

JQL filtering supported.

Worklog webhooks

  • created (worklog_created)
  • updated (worklog_updated)
  • deleted (worklog_deleted)

Note, callbacks for worklog events will soon be restricted to only include the 100 most recent items. See the notice.

Comment webhooks

  • created (comment_created)
  • updated (comment_updated)
  • deleted (comment_deleted)

JQL filtering supported.

Attachment webhooks

  • created (attachment_created)
  • deleted (attachment_deleted)
  • created (issuelink_created)
  • deleted (issuelink_deleted)

Project webhooks

  • created (project_created)
  • updated (project_updated)
  • deleted (project_deleted)

Version webhooks

  • released (jira:version_released)
  • unreleased (jira:version_unreleased)
  • created (jira:version_created)
  • moved (jira:version_moved)
  • updated (jira:version_updated)
  • deleted (jira:version_deleted)
  • merged (jira:version_deleted) note, this is the same webhook event name as the 'deleted' event, but the response will include a mergedTo property*

User webhooks

  • created (user_created)
  • updated (user_updated)
  • deleted (user_deleted)

Jira system configuration webhooks

  • voting (option_voting_changed)
  • watching (option_watching_changed*)
  • unassigned issues (option_unassigned_issues_changed)
  • subtasks (option_subtasks_changed)
  • attachments (option_attachments_changed)
  • issue links (option_issuelinks_changed)
  • time tracking (option_timetracking_changed)

Sprint webhooks

  • created (sprint_created)
  • deleted (sprint_deleted)
  • updated (sprint_updated)
  • started (sprint_started)
  • closed (sprint_closed)

Board webhooks

  • created (board_created)
  • updated (board_updated)
  • deleted (board_deleted)
  • configuration changed (board_configuration_changed)

Jira expressions webhooks

  • evaluation failed (jira_expression_evaluation_failed)

Read more about monitoring Jira expressions here: Monitoring Jira expressions.

Choosing events for your webhook

If you are unsure which events to register your webhook for, then the simplest approach is to register your webhook for all events for the relevant entity (e.g. all issue-related events). Then you won't need to update the webhook if you need to handle these events in future; you can just add code in your app or web application once you want to react to them.

Variable substitution in the webhook URL

You can append variables to the webhook URL when creating or updating a webhook. The variables are listed below:

  • ${board.id}
  • ${issue.id}
  • ${issue.key}
  • ${mergedVersion.id}
  • ${modifiedUser.accountId}
  • ${project.id}
  • ${project.key}
  • ${sprint.id}
  • ${version.id}

You can use these variables to dynamically insert the value of the current issue key, sprint ID, project key, etc, into the webhook URL when it is triggered. Consider the following example:

Say you specified the URL for your webhook with the ${issue.key} variable, like this:

1
https://service.example.com/webhook/${issue.key}

If an issue with the key EXA-1 triggers a webhook, then the URL that will be used is:

1
https://service.example.com/webhook/EXA-1

Retry policy

If a webhook is sent to its callback URL but fails, Jira Cloud will attempt to resend it up to five times. Each subsequent attempt is delayed with a randomized exponential back-off algorithm. The first retry occurs after at least 10 seconds, and the last between 15 and 30 minutes from the first failure.

Retries are attempted when any of the following are true:

  • the callback server returns any of the following status codes: 408, 409, 425, 429, 5xx.
  • the connection fails or times out.

This means that some webhooks might be delivered more than once (if the delivery acknowledgment fails).

Every webhook contains the X-Atlassian-Webhook-Identifier header that provides an identifier for a webhook. This identifier is unique within a Jira Cloud tenant and is the same across retries. After you have processed a webhook, you can use the identifier to filter out retries.

The X-Atlassian-Webhook-Retry header with the current retry count is included with webhooks that have been retried. Monitor this header and cross-reference it with the callback server logs to stay on top of any unexpected reliability problems.

If you are concerned about missed webhooks, we suggest polling Jira occasionally (for example, once a day) for recent updates.

Registering a webhook via the REST API (For Connect apps)

In addition to declaring webhooks statically in the app descriptor, Connect apps can register webhooks dynamically using the REST API. This is perfect if you need to observe a set of issues that is not known beforehand, but instead configured by users at runtime.

Restrictions

Limitations apply to the REST API in order to guarantee stability and performance of Jira Cloud for all apps and users.

Supported events

The following subset of webhook events is supported:

  • jira:issue_created
  • jira:issue_updated
  • jira:issue_deleted
  • comment_created
  • comment_updated
  • comment_deleted

These are all events that allow JQL filtering. Webhooks for other events can be registered in the app descriptor.

Supported JQL queries

Only a subset of JQL clauses is supported. This enables us to optimize event matching on the Jira side.

Supported clauses (left-hand side values):

  • issueKey
  • project
  • issuetype
  • status
  • assignee
  • reporter
  • issue.property (any indexed issue property can be queried)
  • cf[id] (for custom fields – currently only the ID of the Epic custom field is supported here)

Supported operators: =, !=, IN, and NOT IN.

If you would like to use more clauses or operators, raise a request in the Atlassian Connect in Jira project or watch and comment on a relevant issue there.

Registration restrictions

  • One callback URL per app per tenant is allowed.
  • Currently, a maximum of 100 webhooks per app per tenant is allowed. This limit may increase in the future.

Webhook expiration

In order to avoid sending webhooks no one listens to anymore, we assign each webhook an expiration date after which the webhook becomes inactive. The expiration period is 30 days from the time the webhook was created or refreshed using the Extend webhook life REST resource.

Webhooks are available for retrieval for at least 3 months after they expire. Thanks to this, you can wait with refreshing your webhooks until they are needed again; it's not necessary to keep them alive at all times.

Using the REST API: Registration

POST /rest/api/2/webhook

To register webhooks through the REST API, make a POST request to https://your-domain.atlassian.net/rest/api/2/webhook. An example request body is shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
   "url": "https://your-app-base-url.com/webhook-received",
   "webhooks": [
      {
         "jqlFilter": "project = PRJ AND status = Done",
         "events": ["jira:issue_created", "jira:issue_updated"]
      },
      {
         "jqlFilter": "status = Done",
         "events": ["jira:issue_updated"]
      },
      {
         "jqlFilter": "myClause = something",
         "events": ["jira:issue_deleted"]
      }
   ]
}

This request will try to register three webhooks. The response will be a JSON array with a result for every webhook. In this case, we will likely succeed in registering the first two, while the third one will fail due to the unsupported JQL clause. The response will then look like this:

1
2
3
4
5
6
7
8
9
10
11
[
  {
    "createdWebhookId": 1000
  },
  {
    "createdWebhookId": 1001
  },
  {
    "errors": ["The clause myClause is unsupported"]
  }
]

Using the REST API: Fetching registered webhooks

GET /rest/api/2/webhook

To fetch webhooks registered through the REST API, make a GET request to https://your-domain.atlassian.net/rest/api/2/webhook. This API uses pagination. The query parameters startAt and maxResults can be provided to specify the desired page, or the default values of 0 and 100 will be used.

Using the REST API: Deleting registered webhooks

DELETE /rest/api/2/webhook

To delete webhooks registered through the REST API, a DELETE request can be made to https://your-domain.atlassian.net/rest/api/2/webhook.

In your request, include the IDs of the webhooks to be deleted:

1
2
3
{
   "webhookIds": [1000, 1001]
}

IDs corresponding to non-existent webhooks are ignored.

Using the REST API: refreshing registered webhooks

PUT /rest/api/2/webhook

Webhooks registered with the REST API expire after 30 days. Because of that, it's necessary to periodically call the Extend webhook life API to keep them alive. Each call to the API extends the expiration date by another 30 days.

Format of the webhook data sent by Jira

The format is the same as for webhooks declared in the Connect app descriptor, but with an additional property specifying which webhooks were matched for the event. For example, if an event happens in Jira that matches both JQL filters of the webhooks registered, which were successfully registered and assigned the IDs of 1000 and 1001, the webhook data JSON will contain the additional matchedWebhookIds root level property:

1
2
3
4
{
   ... usual webhook data ...,
   "matchedWebhookIds": [1000, 1001]
}

Example Connect app using this REST API

An example Connect app that uses the new REST API to register webhooks, fetch registered webhooks, delete them, and handle them, can be found here.

Registering a webhook via the Jira REST API (Other integrations)

To register a webhook via REST:

  1. POST a webhook in JSON format to: https://your-domain.atlassian.net/rest/webhooks/1.0/webhook

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     {
      "name": "my first webhook via rest",
      "url": "https://www.example.com/webhooks",
      "events": [
        "jira:issue_created",
        "jira:issue_updated"
      ],
      "jqlFilter": "Project = JRA AND resolution = Fixed",
      "excludeIssueDetails" : false
    }
  2. The response will return the webhook in JSON with additional information, including the user that created the webhook, the created timestamp, etc.

To unregister (i.e. delete) a webhook via REST:

  • Execute a DELETE to the following URL: https://your-domain.atlassian.net/rest/webhooks/1.0/webhook/{webhookId}

    The following would delete the webhook with an ID of 70:

    1
    2
    3
    4
    curl --user username:password \
        -X DELETE \
        -H "Content-Type: application/json" \
        https://your-domain.atlassian.net/rest/webhooks/1.0/webhook/70

To query a webhook via REST:

  • To get all webhooks for a Jira instance, perform a GET with the following URL: https://your-domain.atlassian.net/rest/webhooks/1.0/webhook.

    1
    2
    3
    4
    curl --user username:password \
        -X GET \
        -H "Content-Type: application/json" \
        https://your-domain.atlassian.net/rest/webhooks/1.0/webhook
  • To get a specific webhook by its ID, perform a GET with the following URL: https://your-domain.atlassian.net/rest/webhooks/1.0/webhook/{webhookId}

    The following would get a webhook with an ID of 72:

    1
    2
    3
    4
    curl --user username:password \
        -X GET \
        -H "Content-Type: application/json" \
        https://your-domain.atlassian.net/rest/webhooks/1.0/webhook/72

Adding webhooks to workflow post functions

Webhooks can be attached as a workflow post function. This makes it easy to trigger a webhook when an issue makes a workflow transition, for instance when it gets rejected from QA or when it gets resolved.

To add a webhook as a post function to a workflow:

  1. Configure your workflow and select the desired transition in the workflow designer. For more details, see Working with workflows.
  2. Click Post functions, then click Add post function.
  3. Select Trigger a Webhook from the list of post functions and click Add.
  4. Select the desired webhook from the list of webhooks and click Add to add the webhook as a post function.

Notes:

  • If the webhook you choose is also configured to listen to events, then the webhook will be triggered twice: once for the event and once for the workflow transition. For this reason, you should always unselect all events from the webhook admin screen.
  • If a webhook is associated with a post-function, you will not be able to delete the webhook. You must disassociate it from the post-function first.

Executing a webhook

Webhooks will be run without a specific user context, e.g. all issues will be available to the webhook, rather than having them scoped to a single user. (Note the URL will also contain user parameters in the form ?user_id=admin&user_key=admin appended at the end.)

By default, a webhook will send a request with a JSON callback when it is triggered. If you don't want the JSON body sent, then you will need to select Exclude body when configuring the webhook.

At a high level, every callback contains the webhookEvent ID, the timestamp, and information about the entity associated with the event (e.g. issue, project, board, etc). Callbacks may have additional information, depending on the type of event associated with it. As an example, the structure of a callback for an issue-related event is described below:

A callback for an issue-related event is structured like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
    "timestamp",
    "event",
    "user": {
        --> See User shape in table below
    },
    "issue": {
        --> See Issue shape in table below
    },
    "changelog" : {
        --> See Changelog shape in table below
    },
    "comment" : {
        --> See Comment shape in table below
    }
}

Issue shape
User shape
  • The same shape returned from the Jira REST API when a user is retrieved, but without the active, timezone, or groups elements.
  • The shape of the user returned is a condensed shape from the normal user API in REST, but similar to what is returned when the user is embedded in another response.
  • For the full user details, use the Jira REST API and query with the username.
  • REST API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-user-get
  • Example: https://your-domain.atlassian.net/rest/api/3/user?accountId=99:27935d01-92a7-4687-8272-a9b8d3b2ae2e
  • The user is always present in a webhook POST for issue events
  • The user includes an accountType field that is used to distinguish different types of users, such as normal users (atlassian), app users (app), and Jira Service Desk customers (customer).
  • Changelog shape
    • An array of change items, with one entry for each field that has been changed
    • The changelog is only provided for the `issue_updated` event
    • This is similar in format to the changelog you would retrieve from a Jira issue, but without the user (since that is already in the JSON) and without the created timestamp (since that is also already in the JSON for a webhook event)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    {
        "changelog": {
            "items": [
                {
                    "toString": "A new summary.",
                    "to": null,
                    "fromString": "What is going on here?????",
                    "from": null,
                    "fieldtype": "jira",
                    "field": "summary"
                },
                {
                    "toString": "New Feature",
                    "to": "2",
                    "fromString": "Improvement",
                    "from": "4",
                    "fieldtype": "jira",
                    "field": "issuetype"
                },
            ],
            "id": 10124
        }
    }
    Comment shape

    You can see a full example of this below. This JSON callback shows an update to an issue (some fields changed value) where a comment was also added:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
     {
        "id": 2,
        "timestamp": "2009-09-09T00:08:36.796-0500",
        "issue": {
            "expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog",
            "id":"99291",
            "self":"https://jira.atlassian.com/rest/api/2/issue/99291",
            "key":"JRA-20002",
            "fields":{
                "summary":"I feel the need for speed",
                "created":"2009-12-16T23:46:10.612-0600",
                "description":"Make the issue nav load 10x faster",
                "labels":["UI", "dialogue", "move"],
                "priority": "Minor"
            }
        },
        "user": {
            "self":"https://jira.atlassian.com/rest/api/2/user?accountId=99:27935d01-92a7-4687-8272-a9b8d3b2ae2e",
            "accoundId": "99:27935d01-92a7-4687-8272-a9b8d3b2ae2e",
            "name":"brollins",
            "emailAddress":"bryansemail at atlassian dot com",
            "avatarUrls":{
                "16x16":"https://jira.atlassian.com/secure/useravatar?size=small&avatarId=10605",
                "48x48":"https://jira.atlassian.com/secure/useravatar?avatarId=10605"
            },
            "displayName":"Bryan Rollins [Atlassian]",
            "active" : "true"
        },
        "changelog": {
            "items": [
                {
                    "toString": "A new summary.",
                    "to": null,
                    "fromString": "What is going on here?????",
                    "from": null,
                    "fieldtype": "jira",
                    "field": "summary"
                },
                {
                    "toString": "New Feature",
                    "to": "2",
                    "fromString": "Improvement",
                    "from": "4",
                    "fieldtype": "jira",
                    "field": "issuetype"
                }
            ],
            "id": 10124
        },
        "comment" : {
            "self":"https://jira.atlassian.com/rest/api/2/issue/10148/comment/252789",
            "id":"252789",
            "author":{
                "self":"https://jira.atlassian.com/rest/api/2/user?accoundId=99:27935d01-92a7-4687-8272-a9b8d3b2ae2e",
                "name":"brollins",
                "accountId": "99:27935d01-92a7-4687-8272-a9b8d3b2ae2e",
                "emailAddress":"bryansemail@atlassian.com",
                "avatarUrls":{
                    "16x16":"https://jira.atlassian.com/secure/useravatar?size=small&avatarId=10605",
                    "48x48":"https://jira.atlassian.com/secure/useravatar?avatarId=10605"
                },
                "displayName":"Bryan Rollins [Atlassian]",
                "active":true
            },
            "body":"Just in time for AtlasCamp!",
            "updateAuthor":{
                "self":"https://jira.atlassian.com/rest/api/2/user?accountId=99:27935d01-92a7-4687-8272-a9b8d3b2ae2e",
                "accoundId": "99:27935d01-92a7-4687-8272-a9b8d3b2ae2e",
                "name":"brollins",
                "emailAddress":"brollins@atlassian.com",
                "avatarUrls":{
                    "16x16":"https://jira.atlassian.com/secure/useravatar?size=small&avatarId=10605",
                    "48x48":"https://jira.atlassian.com/secure/useravatar?avatarId=10605"
                },
                "displayName":"Bryan Rollins [Atlassian]",
                "active":true
            },
            "created":"2011-06-07T10:31:26.805-0500",
            "updated":"2011-06-07T10:31:26.805-0500"
        },
        "timestamp": "2011-06-07T10:31:26.805-0500",
        "webhookEvent": "jira:issue_updated"
    }

    Response codes

    If a webhook is triggered successfully, a response code of 200 is returned. All other response codes should be treated as an unsuccessful attempt to trigger the webhook.

    Known issues

    • Post function web hooks will not fire if added to the Create Issue workflow transition. We recommend that you configure your web hook to fire from the issue_created event instead.