
Jira fires webhooks for almost every event in a project: issue creation and updates, comments, transitions, sprint changes, worklog edits, project changes, and more. They're how integrations stay in sync with what's happening in Jira without polling.
Jira webhooks come in two flavors with very different security models. This guide covers both, the JQL filtering you can apply at the source, and what production reliability requires.
What Jira sends
Jira webhooks are JSON POST requests. A jira:issue_updated event:
{
"timestamp": 1714000000000,
"webhookEvent": "jira:issue_updated",
"issue_event_type_name": "issue_assigned",
"user": {
"self": "https://your-tenant.atlassian.net/rest/api/2/user?accountId=...",
"accountId": "5b10a2844c20165700ede21g",
"displayName": "Octavia Chen"
},
"issue": {
"id": "10001",
"key": "PROJ-42",
"fields": {
"summary": "Add deterministic replay",
"status": { "name": "In Progress" },
"assignee": { "accountId": "..." },
"priority": { "name": "High" },
"issuetype": { "name": "Story" }
}
},
"changelog": {
"items": [{ "field": "status", "fromString": "To Do", "toString": "In Progress" }]
}
}
Common Jira event types:
jira:issue_created,jira:issue_updated,jira:issue_deletedcomment_created,comment_updated,comment_deletedworklog_updated,worklog_created,worklog_deletedsprint_started,sprint_closed,sprint_updatedproject_created,project_updated,project_deletedversion_released,version_unreleased
The two flavors of Jira webhooks
This is the part most guides skip. Jira has two distinct webhook setup models with very different security stories.
1. Admin-configured webhooks
A Jira admin sets up a webhook URL in the Jira system settings. Jira POSTs events to that URL.
Security gap: these webhooks are not signed by default. Jira sends the payload over HTTPS but doesn't include a signature header. Anyone who knows your webhook URL can POST to it and spoof events.
Mitigations:
- Use an unguessable URL (long random path token).
- Allowlist Atlassian's webhook source IPs — Atlassian publishes a list of CIDR ranges; only accept POSTs from those addresses.
- Set a custom
Authorization: Bearerheader in the webhook config — Jira lets you add custom headers, so put a shared secret there and verify it server-side.
2. Connect-app webhooks
If you're building a Connect app (an Atlassian-marketplace integration), Jira signs webhooks with a JWT in the Authorization header. The JWT uses your shared secret and includes a body hash.
This is the secure flavor. Verification mirrors the OAuth/JWT pattern: decode the JWT, check the issuer matches your app, verify the signature with your shared secret, verify the qsh (query-string hash) and body claims.
Most teams building one-off integrations use admin-configured webhooks. Most teams shipping marketplace apps use Connect.
JQL filtering at the source
Jira admin-configured webhooks support a JQL filter — only issues matching the JQL trigger the webhook. This is more powerful than filtering at your handler because Jira does the filtering and you only receive relevant events.
Examples:
project = PROJ— only events for one projectpriority in (High, Highest)— only high-priority issuesassignee = currentUser()— only events on issues assigned to the integration userlabels in (production)— only issues with a specific label
Pair JQL filtering with handler-side routing for layered control.
Jira's retry policy
Jira retries failed webhooks (non-2xx) on a backoff schedule. Specific retry counts and timing depend on the Jira product (Cloud vs Server vs Data Center) and the webhook flavor. The window is finite — past it, the event is marked failed and visible in the webhook's audit log.
This means:
- Acknowledge fast — return 2xx in under one second
- Be idempotent — duplicates will arrive during retries
- Have a recovery story — for missed events, query Jira's REST API to backfill (using
updated >= "<timestamp>"JQL)
DIY: minimal Jira webhook handler in Node (admin-configured)
const sharedToken = process.env.JIRA_WEBHOOK_TOKEN!
export async function POST(req: Request) {
// Auth check via custom header (configured in Jira webhook settings)
const auth = req.headers.get('authorization')
if (auth !== `Bearer ${sharedToken}`) {
return new Response('Unauthorized', { status: 401 })
}
// (Optional) Source IP allowlist check via req.headers.get('x-forwarded-for')
const body = await req.text()
const event = JSON.parse(body)
// Idempotency: there's no single event ID — use composite of
// (webhookEvent, issue.id, timestamp)
// ...
queueWork(event)
return new Response('ok', { status: 200 })
}
For Connect apps, replace the Bearer check with JWT verification using atlassian-jwt (Node) or the equivalent library for your language.
Production needs more:
- A persistence layer for the composite idempotency key
- A queue for async work
- A backfill path via Jira's REST API when events are missed
- Routing by
webhookEventand project key - Source-IP enforcement (CIDR allowlist of Atlassian ranges)
Hooksbase: receive Jira webhooks without rebuilding the rest
Jira isn't one of Hooksbase's five pre-verified provider packs (Stripe, GitHub, Clerk, Slack, Resend), and Hooksbase does not forward Jira's original auth headers to your destination. If you need a custom bearer check or Connect JWT verification, do it in a small pre-ingest forwarder, then post the verified raw body to Hooksbase with the bearer ingest secret.
Setup:
- Create a webhook in Hooksbase with your application URL as the destination
- Add your verification forwarder URL to your Jira webhook configuration or Connect app descriptor
- In the forwarder, verify the shared bearer header or Connect JWT, then POST the same raw body to the Hooksbase ingest URL with
Authorization: Bearer <ingest secret>
You get:
- Acknowledge in milliseconds to Jira regardless of how slow your downstream is
- Idempotency for your destination — every dispatch includes a unique
webhook-idheader (Standard Webhooks-compatible) you can dedupe on across retries - Retries with exponential backoff to your endpoint after Hooksbase accepts the event
- Routing rules by
webhookEvent, project key, or any payload field — sendjira:issue_createdto one destination,comment_createdto another, project-specific events to a third - Payload transforms — extract just the issue fields your downstream needs from Jira's verbose payload
- Deterministic replay — re-run a failed delivery with the same payload bytes while the payload is retained
- Delivery history and DLQ
Common Jira webhook use cases for AI agents
- Issue triage agent —
jira:issue_createdevents trigger an agent that classifies the issue, sets the right component/labels, and assigns to the on-call engineer - Status sync agent —
jira:issue_updatedevents withstatuschanges trigger an agent that propagates the status to GitHub PRs, Slack channels, or your customer-facing status page - Sprint planning agent —
sprint_startedevents trigger an agent that summarizes the sprint goals and posts a kickoff message in Slack - Comment response agent —
comment_createdevents on customer-tagged issues trigger an agent that drafts a reply for the assignee to review - Worklog audit agent —
worklog_updatedevents trigger an agent that flags anomalies (unusually high or low time entries) for managers
Where to go next
- How to receive GitHub webhooks reliably for the canonical signed-provider pattern
- How to receive Slack events reliably for cross-tool event flows
- Route events to the right agent for the routing model
- How to build an AI agent for the full agent build path
Start free at app.hooksbase.com.