# PagerDuty incident acknowledged or resolved notifications on your phone

> Get a quiet push notification when a PagerDuty incident is acknowledged or resolved, and a louder one when an acknowledgement runs out.

- Company: PagerDuty (https://www.justpush.io/recipes/pagerduty)
- Event: `incident.resolved` (incident.acknowledged, incident.unacknowledged and incident.resolved)
- Tags: Monitoring & observability, Issues & tickets, Uptime & downtime
- Install: https://studio.justpush.io/recipes/pagerduty/incident-update
- Web page: https://www.justpush.io/recipes/pagerduty/incident-update

## 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 PagerDuty, go to Integrations > Generic Webhooks (v3) and click New Webhook (or add one on a service's Integrations tab).
3. Paste your webhook URL (shown in Studio after install) as the Webhook URL.
4. Under Event Subscription, tick incident.acknowledged, incident.unacknowledged and incident.resolved and click Add Webhook.
5. Create a v3 webhook under **Integrations → Generic Webhooks (v3) → New Webhook** (Managers and Admins), or on a single service under **Services → Service Directory → your service → Integrations → Webhooks**. Paste the URL above and tick **incident.acknowledged**, **incident.unacknowledged** and **incident.resolved**. The recipe ignores every other event type, so new incidents are left to the **Incident triggered** recipe.
6. To only hear about resolved incidents, use this URL instead:
7. ```
8. your webhook URL (shown in Studio after install)?resolved=1
9. ```
10. *Unacknowledged* means the incident is back to triggered (the acknowledgement timed out or someone undid it), so that push is High priority. The others are Low.
11. To check the connection, open the webhook, click **Send Test Event** under **Test**, and you'll get a quiet "PagerDuty connected" push.
12. PagerDuty signs every v3 webhook with an `X-PagerDuty-Signature` header (HMAC-SHA256 with the subscription's secret), 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
// PagerDuty → incident.acknowledged, incident.unacknowledged, incident.resolved
// Fires when someone acknowledges a PagerDuty incident, the acknowledgement runs out (or is
// undone), or the incident is resolved.
// Docs: https://docs.pagerduty.com/developer/webhooks-overview

// Each event, how to label it, and how loud to be.
const EVENTS = {
    // Low: someone is already on it.
    "incident.acknowledged": { emoji: "👀", verb: "Acknowledged", priority: -1 },
    // High: the incident is back to triggered and nobody is on it any more.
    "incident.unacknowledged": { emoji: "⚠️", verb: "Unacknowledged", priority: 1 },
    // Low: it's over.
    "incident.resolved": { emoji: "✅", verb: "Resolved", priority: -1 },
}

// Look a key up without tripping over inherited names like "toString".
function pick(table, key, fallback) {
    return typeof key === "string" && 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 }] : []
}

// A trimmed string, or null for anything else.
function text(value) {
    return typeof value === "string" && value.trim() ? value.trim() : null
}

// "12m" / "1h 5m" / "2d 3h" between two timestamps.
function between(start, end) {
    const minutes = Math.round((Date.parse(end) - Date.parse(start)) / 60000)
    if (!Number.isFinite(minutes) || minutes < 0) return null
    if (minutes < 60) return `${Math.max(minutes, 1)}m`
    if (minutes < 1440) return `${Math.floor(minutes / 60)}h ${minutes % 60}m`
    return `${Math.floor(minutes / 1440)}d ${Math.floor((minutes % 1440) / 60)}h`
}

function handleRequest(request) {
    const body = request.body && typeof request.body === "object" ? request.body : {}
    const event = body.event && typeof body.event === "object" ? body.event : {}
    const type = event.event_type

    // "Send Test Event" on the subscription sends a pagey.ping; confirm the connection quietly.
    if (type === "pagey.ping") {
        return {
            title: "🔔 PagerDuty connected",
            message: text(event.data?.message) ?? "Your webhook subscription is working",
            topic: "PagerDuty",
            priority: -1,
        }
    }

    // Guard: only acknowledge / unacknowledge / resolve. v3 webhooks have no event header,
    // so the payload's event_type decides.
    const state = pick(EVENTS, type, null)
    const incident = event.data
    if (!state || !incident || typeof incident !== "object") {
        console.log(`Ignoring PagerDuty "${type ?? "unknown"}" event`)
        return null
    }

    // ?resolved=1 on the webhook URL only pushes when incidents are resolved.
    const onlyResolved = ["1", "true", "yes"].includes(String(request.query?.resolved ?? "").toLowerCase())
    if (onlyResolved && type !== "incident.resolved") {
        console.log(`Skipping "${type}" because of ?resolved=1`)
        return null
    }

    const title = text(incident.title) ?? text(incident.summary) ?? "An incident"
    // agent is null when PagerDuty or an integration did it (e.g. an auto-resolve).
    const who = text(event.agent?.summary)
    const took = type === "incident.resolved" ? between(incident.created_at, event.occurred_at) : null
    const what = [state.verb, who ? `by ${who}` : null, took ? `after ${took}` : null].filter(Boolean).join(" ")
    const number = Number.isFinite(incident.number) ? `#${incident.number}` : null

    return {
        title: `${state.emoji} ${title}`,
        message: [what, number, text(incident.service?.summary)].filter(Boolean).join(" · "),
        topic: "PagerDuty",
        priority: state.priority, // see EVENTS above
        buttons: link(incident.html_url, "Open incident"),
    }
}
```

## PagerDuty incident.resolved webhook payload (sample)

```json
{
  "event": {
    "id": "01D8XQ5T8MB2V6C1Y3F7K0EFGH",
    "data": {
      "id": "PGR0VU2",
      "self": "https://api.pagerduty.com/incidents/PGR0VU2",
      "type": "incident",
      "teams": [
        {
          "id": "PFCVPS0",
          "self": "https://api.pagerduty.com/teams/PFCVPS0",
          "type": "team_reference",
          "summary": "Payments",
          "html_url": "https://acme.pagerduty.com/teams/PFCVPS0"
        }
      ],
      "title": "Checkout API error rate above 5%",
      "number": 1234,
      "status": "resolved",
      "service": {
        "id": "PF9KMXH",
        "self": "https://api.pagerduty.com/services/PF9KMXH",
        "type": "service_reference",
        "summary": "Checkout API",
        "html_url": "https://acme.pagerduty.com/services/PF9KMXH"
      },
      "urgency": "high",
      "html_url": "https://acme.pagerduty.com/incidents/PGR0VU2",
      "priority": {
        "id": "PSO75BM",
        "self": "https://api.pagerduty.com/priorities/PSO75BM",
        "type": "priority_reference",
        "summary": "P1",
        "html_url": "https://acme.pagerduty.com/account/incident_priorities"
      },
      "assignees": [
        {
          "id": "PTUXL6G",
          "self": "https://api.pagerduty.com/users/PTUXL6G",
          "type": "user_reference",
          "summary": "Sam Lee",
          "html_url": "https://acme.pagerduty.com/users/PTUXL6G"
        }
      ],
      "created_at": "2026-09-29T10:51:28Z",
      "incident_key": "d3640fbd41094207a1c11e58e46b1662",
      "resolve_reason": null,
      "conference_bridge": null,
      "escalation_policy": {
        "id": "PUS0KTE",
        "self": "https://api.pagerduty.com/escalation_policies/PUS0KTE",
        "type": "escalation_policy_reference",
        "summary": "Checkout on-call",
        "html_url": "https://acme.pagerduty.com/escalation_policies/PUS0KTE"
      }
    },
    "agent": {
      "id": "PTUXL6G",
      "self": "https://api.pagerduty.com/users/PTUXL6G",
      "type": "user_reference",
      "summary": "Sam Lee",
      "html_url": "https://acme.pagerduty.com/users/PTUXL6G"
    },
    "client": {
      "name": "PagerDuty"
    },
    "event_type": "incident.resolved",
    "occurred_at": "2026-09-29T11:33:52.402Z",
    "resource_type": "incident"
  }
}
```

## 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 PagerDuty 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.
