Auto-Assigning Jira Tickets from GitHub PR Events with n8n
TL;DR — Workflow: GitHub webhook → parse branch name for Jira key → call Jira API to assign + transition. Five n8n nodes. Saves the team ~10 manual ticket updates per day. Specific working JSON + the gotchas with GitHub webhook payloads.
After connecting n8n to Jira, here’s a concrete workflow that uses it. Real-world use case: engineers open PRs and forget to move the Jira ticket from “In Progress” to “In Review.” Reviewers find no link between PR and ticket. Standup gets confusing.
This workflow eliminates the manual step. When a PR opens, the workflow looks at its branch name (which by team convention includes the Jira key like PROJ-123-fix-billing), assigns the ticket to the PR author, and transitions it. Five nodes, twenty minutes to build.
The convention this depends on
Two team conventions need to be in place for this to work:
- Branch naming:
<JIRA-KEY>-<short-description>. Examples:PROJ-123-add-redis-cache,BUG-42-fix-login-csrf. - Jira user emails match GitHub user emails, OR you have a mapping table.
Without these, the workflow has nothing to key off of.
For (1), enforce with a branch-name lint hook in the repo. For (2), maintain a small mapping table if your team uses different email domains. We use a dedicated user-mapping workflow that runs nightly — covered later.
The workflow shape
[Webhook Trigger: GitHub PR event]
↓
[IF: action is "opened" or "reopened"]
↓
[Code: extract Jira key from PR branch name]
↓
[IF: jira_key is non-empty]
↓
[Jira: Assign issue + Transition to In Review]
↓
[Slack: post confirmation in PR-related channel]
Six nodes including the trigger. Let me walk through each.
Step 1: GitHub webhook setup
In n8n, create a new workflow. Add a Webhook trigger node:
HTTP Method: POST
Path: github-pr-events
Authentication: Header Auth (configure a secret token)
Response Mode: Last Node
n8n gives you a production URL like https://n8n.example.com/webhook/github-pr-events.
In GitHub, on the repo (or org for fleet-wide):
Settings → Webhooks → Add webhook
Payload URL: <n8n production URL>
Content type: application/json
Secret: <a random secret>
Events: Pull requests
In n8n’s Webhook node, set the header auth credential to match the GitHub secret. (GitHub signs with HMAC-SHA256; n8n’s built-in header auth doesn’t do HMAC verification by itself — covered in the security post. For now, a shared bearer-style secret works for an internal-only setup.)
Step 2: Filter to “opened” / “reopened”
GitHub fires the webhook for every PR action — opened, edited, synchronize, ready_for_review, closed, reopened, labeled, etc. We only care about open events.
Add an IF node:
Condition: {{ $json.body.action }}
Operation: is one of
Values: opened, reopened, ready_for_review
(ready_for_review covers the case where a draft PR is marked ready — that’s “newly entering review” in our team’s mental model.)
If false, the workflow ends here. If true, continue.
Step 3: Extract Jira key from branch name
A Code node:
const item = $input.first();
const pr = item.json.body.pull_request;
const branch = pr.head.ref; // e.g., "PROJ-123-add-redis-cache"
// Match a Jira key at the start: 2-10 uppercase letters, dash, digits
const match = branch.match(/^([A-Z]{2,10}-\d+)/);
if (!match) {
return [{ json: { jira_key: '', branch, pr_number: pr.number, pr_url: pr.html_url, author_email: pr.user.email || pr.user.login } }];
}
return [{
json: {
jira_key: match[1],
branch,
pr_number: pr.number,
pr_url: pr.html_url,
pr_title: pr.title,
author_email: pr.user.email || pr.user.login + '@example.com',
},
}];
Returns the extracted Jira key or empty string. (pr.user.email is often null from GitHub’s public API for privacy — fall back to constructing email from username + your domain, or use a mapping table.)
Step 4: Filter to non-empty jira_key
Another IF:
Condition: {{ $json.jira_key }}
Operation: is not empty
If branch didn’t match the convention, workflow ends. Optionally branch a Slack notification to the author here: “Hey, your branch frontend-fix-button doesn’t match our naming convention; please rename.”
Step 5: Assign + transition in Jira
Two Jira nodes back-to-back (or one HTTP node hitting /issue/{key} + /issue/{key}/transitions).
Assign:
Node: Jira
Resource: Issue
Operation: Update
Issue Key: {{ $json.jira_key }}
Update Fields:
Assignee (account ID): <look up by email — see below>
Jira’s REST API v3 uses accountId for users, not email. Two ways to handle:
- Cache a mapping table in a Code node or a simple JSON workflow data file:
{ "alice@example.com": "5c2a1234abcd5678" } - Look up dynamically via
GET /rest/api/3/user/search?query=<email>before each assign
For low volume, the lookup is fine. For high volume, cache and refresh nightly.
Transition:
Node: Jira
Resource: Issue
Operation: Transition
Issue Key: {{ $json.jira_key }}
Transition: In Review
The transition name has to match exactly what your workflow defines. If your project uses “Code Review” instead, use that name.
Step 6: Confirm in Slack
A Slack node posts to the author or to a team channel:
Resource: Message
Operation: Post
Channel: #eng-pr
Text: 🤖 <{{ $json.pr_url }}|PR #{{ $json.pr_number }}> opened — moved {{ $json.jira_key }} to *In Review*
Useful for the team to see the automation working. Becomes invisible once it’s reliable; that’s the goal.
Testing
Don’t activate immediately. Test by:
- Open n8n’s workflow editor, click “Execute Workflow” — n8n waits for one webhook
- Open a PR in GitHub on a test repo (or replay a recent PR event via GitHub’s webhook delivery panel)
- n8n receives, you see the data flow through nodes
- Verify Jira ticket was assigned + transitioned
- Verify Slack notification
Once it works once, activate.
Failure modes
A few real cases we’ve hit:
- PR opened with no Jira key in branch. Workflow exits cleanly at step 4. Could post a polite Slack DM to author; we don’t.
- Jira ticket already in a different status that can’t transition to “In Review.” Jira returns 400. n8n marks execution as failed; we have a Slack #automation-alerts channel that pings on consecutive failures (covered Friday).
- GitHub user’s email doesn’t map to a Jira user. Same — Jira returns 400 on the assign. Log + skip transition.
- PR opened for a closed ticket. Workflow still runs. Probably fine; the assign is a no-op if the user is already the assignee.
Most are quiet failures. None paged anyone in two months.
Common Pitfalls
Branch-name regex too loose. [A-Z]{2,10}-\d+ is fine; [A-Z]+-\d+ matches B-1 which is rarely a real Jira key. Be specific.
Trusting GitHub user email. It’s often null. Have a fallback strategy — username-to-email map, or default-domain construction.
No idempotency. GitHub can deliver the same webhook twice (rare but happens). Re-running the workflow re-assigns the ticket (no-op) but re-posts to Slack (visible). For chatty workflows, dedupe by delivery_id.
Forgetting GitHub’s webhook payload size limit. PRs with thousands of files can produce huge payloads. Mostly fine for n8n; can break workflows that try to log the entire body.
Activating before testing on a non-prod PR. “My next real PR” is not a good test. Use a test repo or a dedicated test branch first.
No retry on transient Jira failures. Jira occasionally 503s. n8n’s HTTP node has retry; built-in Jira node doesn’t (in 0.176). Wrap with try/catch in Code if needed, or use HTTP node.
Production traffic to dev n8n. Use separate webhook URLs for staging vs prod. Easy to misconfigure GitHub once and not notice.
Wrapping Up
Five-to-six nodes, one webhook + one Code node + two Jira calls + one Slack message. Saves the team ~10 manual updates per day, eliminates “wait, what ticket does this PR fix?” confusion. Friday: Slack slash commands via n8n — the inverse direction, where Slack triggers workflows on demand.