# Statuspage component status notifications on your phone

> Get a push notification when a component on a status page you follow (yours or a vendor's) goes degraded, partially or fully down, into maintenance, or back to operational.

- Company: Statuspage (https://www.justpush.io/recipes/statuspage)
- Event: `component_update` (Webhook subscription)
- Tags: Monitoring & observability, Uptime & downtime
- Install: https://studio.justpush.io/recipes/statuspage/component-status
- Web page: https://www.justpush.io/recipes/statuspage/component-status

## Setup

1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
2. Open the status page you want to follow (your own, or a vendor's Statuspage-hosted page) and click Subscribe to updates.
3. Pick the webhook tab, paste your webhook URL (shown in Studio after install) as the Webhook URL, and enter your email address (Statuspage emails it if deliveries keep failing).
4. No webhook tab? The page doesn't allow webhook subscribers. On your own page, turn on Webhook under Subscribers > Options > Settings first.
5. Statuspage webhooks are a kind of **subscriber**, so this works for any Statuspage-hosted status page that allows webhook subscriptions: your own, or a vendor you depend on (for example `githubstatus.com`).
6. Open the status page and click **Subscribe to updates**, then the **webhook** tab. If there's no webhook tab, the page owner hasn't enabled webhook subscribers. On your own page, turn it on under **Subscribers → Options → Settings → Webhook** and save.
7. Paste the URL above as the **Webhook URL** and enter your **email address**. Statuspage uses it to email you if your endpoint keeps failing. If a confirmation email arrives, confirm it.
8. Subscribe. If the page lets you pick components, choose the ones you care about.
9. One subscription sends **both** incident and component webhooks to the same URL. This recipe only pushes for component status changes; the Incident update recipe handles incidents. A component usually changes status as part of an incident, so with both installed you'll get a push for each.
10. Optional query parameters on the URL above:
11. `?name=GitHub` puts the page's name in the title (the webhook itself doesn't include it).
12. `?only=API Requests,Git Operations` only pushes for those components (names as shown on the page, case-insensitive; URL-encode spaces as `%20`).
13. `?recovered=0` skips the "is operational" pushes when a component recovers.
14. Combine them with `&`, e.g. `?name=GitHub&only=Actions`.
15. Requests aren't signed, so keep your endpoint URL private. After 10 failed deliveries in about an hour the subscription is quarantined and you get an email; re-subscribe to reactivate it.

## 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
// Statuspage → component status changed
// Fires when a component on a status page you subscribed to by webhook changes status
// (operational, degraded, partial or major outage, under maintenance). Works for your own
// page and for any vendor's Statuspage-hosted page that allows webhook subscribers.
// Docs: https://support.atlassian.com/statuspage/docs/enable-webhook-notifications/

// Component statuses: how to phrase them, and how loud to be.
const STATES = {
    major_outage: { emoji: "🔴", title: "has a major outage", was: "a major outage", priority: 2 }, // Highest — it's down right now
    partial_outage: { emoji: "🟠", title: "has a partial outage", was: "a partial outage", priority: 1 }, // High — partly down, you may need to work around it
    degraded_performance: { emoji: "🟡", title: "has degraded performance", was: "degraded performance", priority: 0 }, // Normal — slow, not down
    under_maintenance: { emoji: "🛠️", title: "is under maintenance", was: "maintenance", priority: -1 }, // Low — planned work
    operational: { emoji: "✅", title: "is operational", was: "operational", priority: -1 }, // Low — good news, back to normal
}

// Look a key up without tripping over inherited names such as "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 }] : []
}

// The status page itself, from the unsubscribe link's host (e.g. https://www.githubstatus.com).
function pageUrl(body) {
    const match = typeof body.meta?.unsubscribe === "string" ? body.meta.unsubscribe.match(/^https?:\/\/[^/?#]+/) : null
    return match ? match[0] : null
}

function handleRequest(request) {
    const body = request.body && typeof request.body === "object" ? request.body : {}
    const update = body.component_update && typeof body.component_update === "object" ? body.component_update : null
    const state = pick(STATES, update?.new_status, null)

    // Guard: only component webhooks. Incident webhooks (incident) and junk stay quiet.
    if (!update || !state) {
        console.log(`Ignoring Statuspage payload — not a component update (${update?.new_status ?? "no component_update"})`)
        return null
    }

    if (update.old_status === update.new_status) {
        console.log(`Component status unchanged (${update.new_status}) — skipping`)
        return null
    }

    const query = request.query || {}
    const component = typeof body.component?.name === "string" && body.component.name.trim() ? body.component.name.trim() : "A component"

    // ?only=API,Git Operations limits pushes to those components (names, case-insensitive).
    const only = typeof query.only === "string"
        ? query.only.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
        : []
    if (only.length && !only.includes(component.toLowerCase())) {
        console.log(`Skipping "${component}" — not in ?only=`)
        return null
    }

    // ?recovered=0 skips the "back to operational" pushes.
    if (update.new_status === "operational" && ["0", "false", "no"].includes(String(query.recovered ?? "").toLowerCase())) {
        console.log("Skipping recovery (?recovered=0)")
        return null
    }

    const page = typeof query.name === "string" && query.name.trim() ? `${query.name.trim()}: ` : ""
    const was = pick(STATES, update.old_status, null)
    const overall = typeof body.page?.status_description === "string" ? body.page.status_description.trim() : ""

    return {
        title: `${state.emoji} ${page}${component} ${state.title}`,
        message: [was ? `Was ${was.was}` : null, overall ? `Page status: ${overall}` : null].filter(Boolean).join(" · ") ||
            "Component status changed",
        topic: "Statuspage",
        priority: state.priority,
        buttons: link(pageUrl(body), "Status page"),
    }
}
```

## Statuspage component_update webhook payload (sample)

```json
{
  "meta": {
    "unsubscribe": "https://status.example.com/?unsubscribe=j0vqr9kl3513",
    "documentation": "https://doers.statuspage.io/customer-notifications/webhooks/"
  },
  "page": {
    "id": "j2mfxwj97wnj",
    "status_indicator": "major",
    "status_description": "Partial System Outage"
  },
  "component": {
    "id": "rb5wq1dczvbm",
    "name": "API Requests",
    "status": "major_outage",
    "created_at": "2024-05-29T21:32:28Z"
  },
  "component_update": {
    "id": "k7730b5v92bv",
    "created_at": "2026-09-29T21:32:28Z",
    "new_status": "major_outage",
    "old_status": "operational",
    "component_id": "rb5wq1dczvbm"
  }
}
```

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