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.

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