# Jira issue assigned notifications on your phone

> Get a push notification when a Jira issue is assigned, optionally only when it's assigned to you.

- Company: Jira (https://www.justpush.io/recipes/jira)
- Event: `jira:issue_updated` (Issue: updated)
- Tags: Project management, Issues & tickets
- Install: https://studio.justpush.io/recipes/jira/issue-assigned
- Web page: https://www.justpush.io/recipes/jira/issue-assigned

## Setup

1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
2. In Jira, go to Settings > System > WebHooks and click Create a WebHook (Jira admins only).
3. Paste your webhook URL (shown in Studio after install) as the URL. Optionally limit it with a JQL filter such as project = ABC.
4. Tick Issue: updated and save.
5. Create an admin webhook under **Settings → System → WebHooks** and tick **Issue: updated**. To only hear about issues assigned to **you**, add your Jira display name (or your Atlassian account ID, from your profile URL) to the URL:
6. ```
7. your webhook URL (shown in Studio after install)?assignee=Sam%20Lee
8. ```
9. Jira webhooks don't include email addresses, so matching by email won't work. Issues that are created with an assignee already set don't push; only later assignee changes do.
10. Admin webhooks can have a **Secret**, which Jira uses to sign each delivery (`X-Hub-Signature: sha256=…`), but JustPush doesn't check that signature yet, so keep your endpoint URL private.

## Code

Studio calls `handleRequest(request)` with the incoming webhook (`{ method, headers, body, query, raw }`) and sends the message object it returns. Returning `null` sends nothing.

```js
// Jira → jira:issue_updated (issue assigned)
// Fires when an issue's assignee changes to someone. Add ?assignee=Your Name (or your Atlassian
// account ID) to the URL to only hear about issues assigned to you.
// Docs: https://developer.atlassian.com/cloud/jira/platform/webhooks/

// Jira's default priority names → push priority. Custom names fall back to Normal.
const PRIORITIES = {
    Highest: 1,
    Blocker: 1,
    Critical: 1,
    High: 0,
    Medium: 0,
    Low: -1,
    Lowest: -1,
    Minor: -1,
    Trivial: -1,
}

// Look a key up in a table without tripping over inherited names like "toString".
function pick(table, key, fallback) {
    return Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback
}

// Only link to real http(s) URLs.
function link(url, cta) {
    return typeof url === "string" && /^https?:\/\//.test(url) ? [{ cta, url }] : []
}

// issue.self is the REST URL (https://acme.atlassian.net/rest/api/2/issue/10001); the browse
// link lives on the same site: https://acme.atlassian.net/browse/ABC-123.
function browse(issue) {
    const match = typeof issue?.self === "string" ? issue.self.match(/^(https?:\/\/.+?)\/rest\//) : null
    return match && typeof issue.key === "string" ? `${match[1]}/browse/${issue.key}` : null
}

function handleRequest(request) {
    const body = request.body && typeof request.body === "object" ? request.body : {}
    const issue = body.issue && typeof body.issue === "object" ? body.issue : null
    const items = Array.isArray(body.changelog?.items) ? body.changelog.items : []
    const change = items.find((item) => item && (item.fieldId === "assignee" || item.field === "assignee"))
    const fields = issue?.fields ?? {}

    // Guard: only issue updates where the assignee changed to someone (not unassigned).
    if (body.webhookEvent !== "jira:issue_updated" || !issue || !change || !(change.to || fields.assignee)) {
        console.log("Not a Jira issue assignment — skipping")
        return null
    }

    // Read "toString" as the changelog's own field, never Object.prototype.toString.
    const changed = pick(change, "toString", null)
    const name = typeof changed === "string" && changed ? changed : fields.assignee?.displayName
    const accountId = change.to ?? fields.assignee?.accountId

    // ?assignee=Sam Lee (or an account ID) limits pushes to one person's assignments.
    // Jira webhooks don't include email addresses, so match on the name or account ID.
    const only = typeof request.query?.assignee === "string" ? request.query.assignee.trim().toLowerCase() : ""
    const matches = [name, accountId, fields.assignee?.emailAddress].some((value) => typeof value === "string" && value.toLowerCase() === only)

    if (only && !matches) {
        console.log("Assigned to someone else — skipping")
        return null
    }

    const details = [
        fields.summary,
        fields.priority?.name,
        fields.status?.name,
        body.user?.displayName ? `by ${body.user.displayName}` : null,
    ].filter((value) => typeof value === "string" && value)

    return {
        title: `👤 ${issue.key ? `${issue.key} ` : ""}assigned to ${typeof name === "string" && name ? name : "someone"}`,
        message: details.join(" · ") || "Issue assigned",
        topic: "Jira",
        priority: pick(PRIORITIES, fields.priority?.name, 0), // follows the issue's priority
        buttons: link(browse(issue), "Open issue"),
    }
}
```

## Jira jira:issue_updated webhook payload (sample)

```json
{
  "user": {
    "self": "https://acme.atlassian.net/rest/api/2/user?accountId=5b10ac8d82e05b22cc7d4ef5",
    "active": true,
    "accountId": "5b10ac8d82e05b22cc7d4ef5",
    "accountType": "atlassian",
    "displayName": "Mark V"
  },
  "issue": {
    "id": "10231",
    "key": "SHOP-88",
    "self": "https://acme.atlassian.net/rest/api/2/issue/10231",
    "fields": {
      "status": {
        "id": "10000",
        "name": "To Do",
        "statusCategory": {
          "id": 2,
          "key": "new",
          "name": "To Do"
        }
      },
      "project": {
        "id": "10000",
        "key": "SHOP",
        "name": "Webshop"
      },
      "summary": "Checkout total is off by one cent",
      "assignee": {
        "active": true,
        "accountId": "5b10a2844c20165700ede21g",
        "displayName": "Sam Lee"
      },
      "priority": {
        "id": "2",
        "name": "High"
      },
      "issuetype": {
        "id": "10004",
        "name": "Bug"
      }
    }
  },
  "changelog": {
    "id": "10124",
    "items": [
      {
        "to": "5b10a2844c20165700ede21g",
        "from": null,
        "field": "assignee",
        "fieldId": "assignee",
        "toString": "Sam Lee",
        "fieldtype": "jira",
        "fromString": null
      }
    ]
  },
  "timestamp": 1759051213000,
  "webhookEvent": "jira:issue_updated",
  "issue_event_type_name": "issue_assigned"
}
```

## FAQ

### Does this work on iPhone and Android?

Yes. Install the JustPush app from the App Store or Google Play and sign in. Every phone signed in to your account gets the notification.

### Do I need to write code?

No. Install the recipe in Studio, paste your webhook URL into Jira and you are done. The code is there if you want to change the text, the sound or the buttons.

### Can I change what the notification says?

Yes. After install the recipe's code is yours. Edit it in Studio and test it against the sample payload before you save.

### What does it cost?

JustPush is free for 30 days. After that it's $19.99 a year, or $39.99 once. There is no extra charge for recipes.
