Blog

Jira Webhooks: Setup, Payload and Security

How to set up Jira webhooks — admin UI vs REST API, JQL filters, the payload format, X-Hub-Signature verification and testing. A complete, practical guide.

5 min read
Karolis Rusenas
On this page

Jira webhooks guide

Jira can notify your code the moment an issue is created, updated, transitioned or commented on. That is what Jira webhooks are: HTTP POSTs that Jira Cloud (or Data Center) sends to a URL you choose, carrying the issue as JSON. This guide covers the parts people actually get stuck on — the two ways to register a webhook, JQL filtering, what the payload really contains, and how to secure and test the whole thing.

If your immediate goal is receiving Jira events on localhost, we have a focused walkthrough for that: Test Jira webhooks locally. This guide is the broader reference.

Two ways to register a Jira webhook

1. The admin UI (classic webhooks). Go to Settings → System → Webhooks → Create a WebHook. You give it a name, a URL, select events (issue created/updated/deleted, comment events, project/version/user events, and more) and optionally a JQL filter. This is the fastest path and fine for internal tooling — but these webhooks are not signed, and they are instance-wide, so you need Jira admin rights.

2. The REST API (dynamic webhooks). Apps using OAuth 2.0 or Connect register webhooks with POST /rest/api/3/webhook. Dynamic webhooks support a signing secret (verification below), are scoped to the app that created them, and expire — Jira Cloud requires apps to refresh them periodically (they last 30 days unless extended), so schedule a refresh job.

There is a third, often-overlooked option: Automation rules. Jira's automation can fire a "Send web request" action on any rule trigger, which behaves like a webhook with full control over method, headers and body. If you need custom headers (say, an Authorization token) this is the easiest way to get them — classic webhooks send no custom headers.

Scope deliveries with JQL

A webhook without a filter receives events for the whole instance. Add a JQL filter at registration time to narrow it:

project = PROJ AND issuetype = Bug

Only events for matching issues are delivered. This is the single best lever for keeping your handler simple — filter in Jira, not in code.

The payload

Every delivery is JSON with a webhookEvent field. An issue update looks like this (trimmed):

{
  "timestamp": 1768471800000,
  "webhookEvent": "jira:issue_updated",
  "user": { "accountId": "5f00...aa", "displayName": "Alex Doe" },
  "issue": {
    "key": "PROJ-42",
    "fields": {
      "summary": "Checkout button unresponsive on mobile",
      "status": { "name": "In Progress" },
      "priority": { "name": "High" },
      "assignee": { "displayName": "Alex Doe" }
    }
  },
  "changelog": {
    "items": [
      { "field": "status", "fromString": "To Do", "toString": "In Progress" }
    ]
  }
}

Things worth knowing:

  • changelog is where the diff lives. For jira:issue_updated it lists each changed field with fromString/toString. If you only care about status transitions, read the changelog instead of diffing the whole issue.
  • Comment events carry a comment object alongside the issue.
  • Deliveries include tracing headers — X-Atlassian-Webhook-Identifier uniquely identifies the delivery attempt, which is handy for idempotency and log correlation.
  • Payloads can be large (full issue with all custom fields). Budget for tens of kilobytes.

Want to see the real thing without writing a handler? Open a free Webhook Bin, point a test webhook at it and trigger an event — or use the bin's sample catalog, which includes real-shaped jira:issue_updated and jira:issue_created events you can send to any endpoint with one click.

Securing Jira webhooks

How you secure a Jira webhook depends on how it was created:

  • Dynamic webhooks (REST API) with a secret are signed: Jira computes HMAC-SHA256 over the raw body and sends it as sha256=… in the X-Hub-Signature header. Recompute it with your secret and compare in constant time. You can sanity-check an implementation with the free HMAC signature verifier, and the general pitfalls (raw body, timing-safe compare) are covered in Verify a webhook signature.
  • Classic webhooks (admin UI) are not signed. Mitigate with a secret token in the URL path or query string (/hooks/jira?token=…) that your handler checks, and/or restrict inbound traffic to Atlassian's published IP ranges.
  • Automation "Send web request" lets you set your own auth header — treat it like any API client.

Delivery behavior and reliability

Jira Cloud expects your endpoint to answer quickly with a 2xx. Slow or failing endpoints get their deliveries dropped — Jira's retry behavior is limited, so do not treat webhook delivery as guaranteed. The standard hardening pattern:

  1. Return 200 immediately, queue the event, process asynchronously.
  2. Deduplicate on X-Atlassian-Webhook-Identifier (retried attempts reuse context).
  3. Reconcile periodically against the REST API for anything missed.

If you need stronger guarantees than Jira gives you, put a stable relay endpoint in front of your handler: Webhook Relay stores every delivery and retries towards your destination for up to 30 days, so a redeploy or an outage on your side no longer loses events.

Testing your integration

The fast loop:

  1. Inspect first — point the webhook at a Webhook Bin and trigger real events to see exact headers and bodies.
  2. Develop locally — forward events to your machine with relay forward --bucket jira http://localhost:8080/webhook; the local testing guide walks through it.
  3. Replay captured deliveries against your handler while you iterate, instead of re-triggering issues in Jira.

Frequently asked questions

How do I create a webhook in Jira?

Two ways. In the admin UI: Settings → System → Webhooks → Create a WebHook, then set a URL, pick events and optionally a JQL filter. Or via the REST API (POST /rest/api/3/webhook), which creates a dynamic webhook that supports a signing secret. The admin UI path needs Jira admin rights; the REST path registers webhooks on behalf of an OAuth 2.0/Connect app.

What does a Jira webhook payload look like?

JSON with a webhookEvent field naming the event (e.g. jira:issue_updated), a timestamp, the user who triggered it, the affected issue (key, fields like summary, status, priority, assignee), and — for updates — a changelog listing exactly which fields changed from what to what.

Are Jira webhooks signed?

Only dynamic webhooks registered through the REST API with a secret are signed — HMAC-SHA256 of the body in the X-Hub-Signature header. Webhooks created in the admin UI are not signed: secure them with a hard-to-guess token in the URL and/or by allowlisting Atlassian IP ranges.

Why is my Jira webhook not firing?

The usual suspects: the JQL filter does not match the issue, the event type was not selected, the endpoint returned a non-2xx and deliveries are failing, or (on Cloud) the URL is not reachable from the public internet. Point the webhook at a request bin first to confirm Jira is sending at all, then narrow it down.