# Better Stack incident notifications on your phone > Get a Highest-priority push notification when a Better Stack incident starts, and quiet ones when it's acknowledged or resolved, plus incident comments. - Company: Better Stack (https://www.justpush.io/recipes/betterstack) - Event: `incident_change` (Incident) - Tags: Developer tools, Monitoring - Install: https://studio.justpush.io/recipes/betterstack/incident - Web page: https://www.justpush.io/recipes/betterstack/incident ## 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 Better Stack Uptime, go to Integrations > Exporting data > Outgoing webhooks and add one. 3. Paste your webhook URL (shown in Studio after install) as the URL and keep the default template. 4. Choose which Incident events to send and save. 5. In Better Stack Uptime, go to **Integrations โ†’ Exporting data โ†’ Outgoing webhooks**, add one with the URL above, and pick the incident events: started, acknowledged, resolved, reopened and comments. **Keep the default template**; the recipe reads its fields. 6. Incidents from Better Stack Telemetry (log and metric alerts) come through here too. 7. The default template doesn't say which event happened, so the recipe works it out: *resolved* when there's a resolved time, *acknowledged* when there's an acknowledged time, otherwise *started* (a reopened incident looks like a new one). Better Stack doesn't sign webhooks; you can add Basic auth or custom headers, but JustPush doesn't check them, 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 // Better Stack โ†’ incident started, acknowledged, resolved or commented // Fires when a Better Stack Uptime outgoing webhook reports an incident change. Covers // uptime monitors, heartbeats and Telemetry alerts (they all create incidents). // Docs: https://betterstack.com/docs/uptime/webhooks/ // Only link to real http(s) URLs. function link(url, cta) { return typeof url === "string" && /^https?:\/\//.test(url) ? [{ cta, url }] : [] } // The template fills unset timestamps with "" or null; treat both (and leftover $VARS) as missing. function text(value) { const s = value == null ? "" : String(value).trim() return s && s !== "null" && !/^\$[A-Z_]+$/.test(s) ? s : null } // "12m" / "1h 5m" 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 return minutes < 60 ? `${Math.max(minutes, 1)}m` : `${Math.floor(minutes / 60)}h ${minutes % 60}m` } function handleRequest(request) { const body = request.body || {} const incident = body.data?.attributes // Guard: only incident payloads (monitor and on-call webhooks look different). if (body.data?.type !== "incident" || !incident) { console.log("Not a Better Stack incident โ€” skipping") return null } const name = text(incident.name) ?? "Incident" const url = text(incident.url) const buttons = [...link(text(incident.screenshot_url), "View screenshot"), ...link(url, "Open site")] if (body.event === "comment") { const comment = body.comment?.attributes ?? {} return { title: `๐Ÿ’ฌ ${name}`, message: (text(comment.user_email) ? `${comment.user_email}: ` : "") + (text(comment.content) ?? "New comment"), topic: "Better Stack", priority: 0, buttons, } } // The default template has no event name, so the timestamps tell the state. const resolved = text(incident.resolved_at) const acknowledged = text(incident.acknowledged_at) const started = text(incident.started_at) if (resolved) { const took = started ? between(started, resolved) : null return { title: `โœ… ${name} resolved`, message: took ? `Resolved after ${took}` : "Incident resolved", topic: "Better Stack", priority: -1, // Low โ€” it's over buttons: link(url, "Open site"), } } if (acknowledged) { return { title: `๐Ÿ‘€ ${name} acknowledged`, message: text(incident.cause) ?? "Someone is on it", topic: "Better Stack", priority: -1, // Low โ€” someone is already on it buttons, } } return { title: `๐Ÿ”ด ${name}`, message: [text(incident.cause), url].filter(Boolean).join(" ยท ") || "New incident", topic: "Better Stack", priority: 2, // Highest โ€” something is down right now buttons, } } ``` ## Better Stack incident_change webhook payload (sample) ```json { "data": { "id": "123456789", "type": "incident", "attributes": { "url": "https://acme.example.com", "name": "Acme website", "cause": "Status 502", "started_at": "2026-09-29T10:51:28.151Z", "http_method": "get", "resolved_at": null, "response_url": null, "screenshot_url": "https://uptime.betterstack.com/screenshots/123456789.png", "acknowledged_at": null, "response_content": null } } } ``` ## 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 Better Stack 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.