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 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:
changelogis where the diff lives. Forjira:issue_updatedit lists each changed field withfromString/toString. If you only care about status transitions, read the changelog instead of diffing the whole issue.- Comment events carry a
commentobject alongside the issue. - Deliveries include tracing headers —
X-Atlassian-Webhook-Identifieruniquely 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 theX-Hub-Signatureheader. 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:
- Return
200immediately, queue the event, process asynchronously. - Deduplicate on
X-Atlassian-Webhook-Identifier(retried attempts reuse context). - 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:
- Inspect first — point the webhook at a Webhook Bin and trigger real events to see exact headers and bodies.
- Develop locally — forward events to your machine with
relay forward --bucket jira http://localhost:8080/webhook; the local testing guide walks through it. - Replay captured deliveries against your handler while you iterate, instead of re-triggering issues in Jira.
Related reading
- Test Jira webhooks locally — the localhost-focused walkthrough
- Send webhooks to Jira — the reverse direction: creating issues from incoming webhooks
- Confluence webhooks — the same patterns on Jira's sibling
- How to test webhooks and What is a webhook
