Last updated Sep 9, 2021

Rate this page:

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:

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.

Issue property webhooks

  • created or updated (issue_property_set)
  • deleted (issue_property_deleted)

JQL filtering supported.

Worklog webhooks

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

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)
  • moved to trash (project_soft_deleted)
  • restored from trash (project_restored_deleted)
  • archived (project_archived)
  • restored from archive (project_restored_archived)

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

Note that variables are only available to a webhook in the context of its registered events. For example, {issue.key} and {issue.id} are available to webhooks registered for events related to issues.

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.

Webhooks that are not delivered after the maximum number of retries can be found with the Get failed webhooks operation.

Webhook tracing (Connect apps)

To trace the origin of a webhook, Connect apps attach the X-Atlassian-Webhook-Trace HTTP header with a value consisting of a string of up to 255 printable ASCII characters to a REST API request.

The header and its value is then attached to every webhook sent from Jira for the REST API request.

The app then uses the webhook trace header to, for example:

  • Differentiate between webhooks triggered by various REST API requests.
  • Track a webhook’s delivery.
  • Attach other data for use when a webhook arrives.

Registering a webhook using the REST API (for Connect and OAuth 2.0 apps)

In addition to declaring webhooks statically in the app descriptor, Connect and OAuth 2.0 apps can register webhooks dynamically using the REST API. This is useful where you need to observe issues based on how users define and amend the issues' content. For example, you may want to initiate other processing when an issue is given a certain label.

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
  • issue_property_set
  • issue_property_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

  • A maximum of 100 webhooks per app per tenant is allowed for a Connect app. For an OAuth 2.0 app, the limit is 5 webhooks per app per user on a tenant.

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 up to 3 months after they expire. It is not necessary to keep a Webhook alive during this period, you can refresh it when it is needed again.

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. The registered URL must use the same base URL as the app. 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 REST API to register webhooks, fetch registered webhooks, delete them, and handle them, can be found here.

Webhooks authentication for OAuth 2.0 apps

Webhooks for OAuth 2.0 apps are secured by bearer authentication. The token is present in the Authorization header and is signed with the app's client secret. Remember to verify the token to keep your integration secure. The use of a library to verify the token is recommended. A list of suitable libraries can be found on the JWT website.

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

To register a webhook using 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 (that is, delete) a webhook using 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 using 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, for example, all issues will be available to the webhook, rather than having them scoped to a single user.

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.

Every callback contains the webhookEvent ID, timestamp, and information about the entity associated with the event (for example issue, project, or board). The callback can have additional information, depending on the type of event associated with it. For example, issue-related events contain the issue_event_type_name field. This field indicates the Jira event that triggered the callback. This is the structure of a callback for an issue-related event:

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

Issue shape
User shape
Changelog shape
  • An array of changed items, with one entry for each field that has been changed.
  • The changelog is only provided for the jira: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)

Comment shape

The returned fields depend on the webhook type. The following is an example of the JSON sent in an issue update callback:

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
 {
    "issue": {
        "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": {
                "self": "https://jira.atlassian.com/rest/api/2/priority/3",
                "iconUrl": "https://jira.atlassian.com/images/icons/priorities/minor.svg",
                "name": "Minor",
                "id": "3"
            },
        }
    },
    "user": {
        "self":"https://jira.atlassian.com/rest/api/2/user?accountId=99:27935d01-92a7-4687-8272-a9b8d3b2ae2e",
        "accoundId": "99:27935d01-92a7-4687-8272-a9b8d3b2ae2e",
        "accountType": "atlassian",
        "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",
        "timeZone": "Europe/Warsaw",
    },
    "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
    },
    "timestamp": 1606480436302,
    "webhookEvent": "jira:issue_updated",
    "issue_event_type_name": "issue_generic"
}

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.
  • Webhooks larger than 25MBs are not delivered. This limit may change without notice.

Rate this page: