Last updated Jul 22, 2024


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.


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

Webhooks created in Jira Administration or registered by /rest/webhooks/1.0/webhook are called admin webhooks in this documentation.

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.

If you leave the JQL query empty, it'll match all issues from all projects.

This means the webhooks we'll send may reveal sensitive information.

Platform webhooks

  • app access blocked to one or more projects (app_access_to_objects_blocked)
  • app access blocked to one or more issues (app_access_to_objects_in_container_blocked)

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)

JQL filtering supported.

Comment webhooks

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

JQL filtering supported.

Attachment webhooks

  • created (attachment_created)
  • deleted (attachment_deleted)

JQL filtering supported.

  • created (issuelink_created)
  • deleted (issuelink_deleted)

Issue type webhooks

  • created (issuetype_created)
  • updated (issuetype_updated)
  • deleted (issuetype_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*

Filter webhooks

  • created (filter_created)
  • updated (filter_updated)
  • deleted (filter_deleted)

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)
  • issue links (option_issuelinks_changed)
  • time tracking (option_timetracking_changed)
  • time tracking provider (option_timetracking_provider_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:

  • {}
  • {}
  • {}
  • {}
  • {issue.key}
  • {}
  • {}
  • {modifiedUser.accountId}
  • {modifiedUser.key} (deprecated)
  • {} (deprecated)
  • {}
  • {project.key}
  • {property.key}
  • {}
  • {}
  • {}

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:


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


Note that variables are only available to a webhook in the context of its registered events. For example, {issue.key} and {} 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 attempt following the failure is delayed with a randomized back-off algorithm and can happen between 5 and 15 minutes after the previous one.

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.

After we send a webhook and receive a failure, we log the time of failure. Every successful response from the recipient removes that log entry. However, if the recipient doesn’t respond successfully within 30 minutes, we only try to deliver a single attempt per webhook until we record a successful delivery.

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

Reliability and performance

Each webhook contains X-Atlassian-Webhook-Flow header with Primary or Secondary value.

All Primary webhooks should be delivered within 30 seconds.

All Secondary webhooks are a result of long-lasting bulk or cascade operation (bulk issue update, project deletion, issue deletion etc.). For those webhooks, the expected delivery time requirements are relaxed, and the delivery should take no more than 15 minutes.

Note, in those cases, the top level webhook is transferred via Primary flow, and all dependent webhooks are transferred using the Secondary flow. For example, when deleting an issue, the issue_deleted event is transferred as Primary but all dependent commend_deleted, attachment_deleted issuelink_deleted etc. are Secondary.

Concurrency limiting

To make it easier for apps to process a large number of webhooks sent as a result of a sudden influx of events in Jira, a concurrency limiter is applied. For Primary webhooks, a default maximum number of 20 concurrent requests per tenant + webhook URL host pair is allowed. For Secondary webhooks the default limit is 10.

These limits can be changed without notice, depending on the tenant and the webhook host.

Webhook tracing (Connect apps)

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

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

The app can use 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.


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
  • priority
  • assignee
  • reporter
  • (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 The registered URL must use the same base URL as the app. An example request body is shown below:

   "url": "",
   "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:

    "createdWebhookId": 1000
    "createdWebhookId": 1001
    "errors": ["The clause myClause is unsupported"]

To receive a registered webhook, an app must be granted all the scopes for all the events in the webhook. This table shows the scopes required for each event.

Webhook typeRequired scopes
jira:issue_deletedread:issue-details:jira, read:comment:jira, read:epic:jira-software, read:group:jira,, read:issue-type:jira, read:project:jira, read:project-role:jira, read:status:jira, and read:user:jira, read:comment:jira, read:epic:jira-software, read:group:jira,, read:issue-type:jira, read:project:jira, read:project-role:jira, read:status:jira, and read:user:jira, read:comment:jira, read:epic:jira-software, read:group:jira,, read:issue-type:jira, read:project:jira, read:project-role:jira, read:status:jira, and read:user:jira
issue_property_setread:epic:jira-software,,read:issue-type:jira,read:project:jira,read:status:jira, and read:user:jira
issue_property_deletedread:epic:jira-software,,read:issue-type:jira,read:project:jira,read:status:jira, and read:user:jira

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

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

   "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:

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

Webhook JSON format

  • Example of a valid request body

      "name": "my first webhook via rest",
      "description": "description of my first webhook",
      "url": "",
      "events": [
      "filters": {
        "issue-related-events-section": "Project = JRA AND resolution = Fixed"
      "excludeBody" : false,
      "secret" : "G8j4166a5OkXRD4WbqV3"

    Where secret is an optional string used to generate a signature and verify incoming webhook payloads. You can’t retrieve your secret after you generate it - if you lose it, you have to get a new one.

  • Example of a valid response body

      "name": "my first webhook via rest",
      "description": "description of my first webhook",
      "url": "",
      "excludeBody": false,
      "events": [
      "filters": {
        "issue-related-events-section": "Project = JRA AND resolution = Fixed"
      "enabled": true,
      "self": "",
      "lastUpdated": 1706692865788,
      "isSigned": true

    Where isSigned is true if a secret is defined.

To register a webhook using REST:

  1. POST a webhook in JSON format to:

  2. The response will return the webhook in JSON with additional information, including the user that created the webhook, the created timestamp, etc.

To update a webhook using REST:

  1. PUT a webhook in JSON format to:{webhookId}

Pass null or an empty string to remove the secret. Omitting the secret field in a request body for PUT will cause it to stay intact.

To unregister (that is, delete) a webhook using REST:

  • Execute a DELETE to the following URL:{webhookId}

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

    curl --user username:password \
        -X DELETE \
        -H "Content-Type: application/json" \

To query a webhook using REST:

  • To get all webhooks for a Jira instance, perform a GET with the following URL:

    curl --user username:password \
        -X GET \
        -H "Content-Type: application/json" \
  • To get a specific webhook by its ID, perform a GET with the following URL:{webhookId}

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

    curl --user username:password \
        -X GET \
        -H "Content-Type: application/json" \

Secure admin webhooks

You can secure webhooks registered through the REST API or webhooks page by passing the secret field.

About webhook deliveries

Once your server is configured to receive payloads, it will listen for any delivery that's sent to the endpoint you configured. For security reasons, you should only process deliveries from Jira Cloud. To ensure your server only processes deliveries from Jira Cloud, you need to:

  1. Create a secret token for your webhook.
  2. Store the token securely on your server.
  3. Validate incoming webhook payloads against the token to verify they're coming from Jira Cloud.

Creating a secret token

You can create a new webhook with a secret token, or you can add a secret token to an existing webhook. When creating a secret token, you should choose a random string of text with high entropy. You can also use the “Generate secret” button to let us generate the secret for you. Make sure to record the secret somewhere secure - it can't be viewed or retrieved once the webhook is saved.

Updating a secret token

You can update the secret token of an existing webhook by editing the webhook. However, be aware that you’ll need to update any integrations using this secret.

Validating webhook deliveries

Jira Cloud will use your secret token to create a HMAC signature and include it in a X-Hub-Signature header, formatted as method=signature, as defined by WebSub.

In your code that handles webhook deliveries, you should calculate the HMAC of the body using your secret token and the hash algorithm specified as the method. Then, compare the HMAC that Jira Cloud sent with the expected HMAC that you calculated, and ensure that they match. For examples showing how to validate the hashes in various programming languages, see the Examples section.

There are a few important things to keep in mind when validating webhook payloads:

  • The HMAC is generated using your webhook's secret token, the payload contents, and the hash algorithm listed in method.
  • If your language and server implementation specifies a character encoding, ensure that you handle the payload as UTF-8. Webhook payloads can contain Unicode characters.

Testing the webhook payload validation

You can use the following secret, payload, and method values to verify that your implementation is correct:

  • secret: It's a Secret to Everybody
  • payload: Hello World!
  • method: sha256

If your implementation is correct, the signatures that you generate should match the following signature values:

signature: a4771c39fbe90f317c7824e83ddef3caae9cb3d976c214ace1f2937e133263c9

X-Hub-Signature: sha256=a4771c39fbe90f317c7824e83ddef3caae9cb3d976c214ace1f2937e133263c9


You can use your programming language of choice to implement HMAC verification in your code. Following are some examples showing how an implementation might look in various programming languages. Note that these examples assume that Jira is using the sha256 hash algorithm. Jira might start using another method for the HMAC in the future. The example code here will start failing if this happens.

Java example

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;

public class Main {
    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
        final String secret = "It's a Secret to Everybody";
        final String payload = "Hello World!";
        final String givenSignature = "sha256=a4771c39fbe90f317c7824e83ddef3caae9cb3d976c214ace1f2937e133263c9";

        final SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        final Mac mac = Mac.getInstance("HmacSHA256");

        final byte[] digest = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
        final HexFormat hex = HexFormat.of();

        final String calculatedSignature = "sha256=" + hex.formatHex(digest);

        if (!MessageDigest.isEqual(calculatedSignature.getBytes(), givenSignature.getBytes())) {
            System.out.println("Signatures do not match\nExpected signature:" +
                    calculatedSignature + "\nActual: signature: " + givenSignature);
        } else {
            System.out.println("Signatures match");

Python example

import hashlib
import hmac

secret = "It's a Secret to Everybody"
payload = "Hello World!"
given_signature = "sha256=a4771c39fbe90f317c7824e83ddef3caae9cb3d976c214ace1f2937e133263c9"

hash_object =
calculated_signature = "sha256=" + hash_object.hexdigest()

if not hmac.compare_digest(calculated_signature, given_signature):
        "Signatures do not match\nExpected signature:"
        f" {calculated_signature}\nActual: signature: {given_signature}"
    print("Signatures match")

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 Work with issue 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.


  • 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:

    "user": {
        --> See User shape in table below
    "issue": {
        --> See Issue shape in table below
    "changelog" : {
        --> See Changelog shape in table below

Issue shape
  • The same shape returned from the Jira REST API when an issue is retrieved with NO expand parameters.
  • See Get issue in the REST API documentation, for more details.
  • Example:
User shape
  • The same shape returned from the Jira REST API when a user is retrieved, but without the locale, emailAddress, groups, and applicationRoles fields.
    • 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.
  • See Get user in the REST API documentation, for more details.
  • Example:
  • 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 Management customers (customer).
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 same shape returned from the Jira REST API when a comment is retrieved.
  • See Get comment in the REST API documentation, for more details.
  • Example:
  • The comment is provided for comment related webhooks.

Webhook payload

The returned fields depend on the webhook type.

The following is an example of the JSON sent in an issue update callback:

    "issue": {
            "summary":"I feel the need for speed",
            "description":"Make the issue nav load 10x faster",
            "labels":["UI", "dialogue", "move"],
            "priority": {
                "self": "",
                "iconUrl": "",
                "name": "Minor",
                "id": "3"
    "user": {
        "accoundId": "99:27935d01-92a7-4687-8272-a9b8d3b2ae2e",
        "accountType": "atlassian",
        "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"

If a bulk operation triggers the webhook, the field bulkOperationMetaData is added to the webhook payload. For example:

   ... usual webhook data ...,
   "bulkOperationMetaData": {
       "sendMail": false

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.
  • Project deletion cascade does not send issue_deleted webhooks.
  • Attachments added on create issue dialog don’t trigger attachment_created webhooks, but they are listed in attachment field in jira:issue_created webhook’s body.

Rate this page: