incident.io incident declared notifications on your phone.

Get a push notification when an incident is declared in incident.io, with its severity, status and summary, and a Highest-priority one for critical incidents.

How it works

Three steps to your first push

STEP 01

Install the recipe

One click in Studio gives you a personal webhook URL.

STEP 02

Paste your URL into incident.io

Add it as a webhook for public_incident.incident_created_v2.

STEP 03

Get a push on your phone

With the text, sound and buttons from the recipe.

Setup

How to set up the incident.io public_incident.incident_created_v2 webhook

  1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
  2. In incident.io, go to Settings > Webhooks and add an endpoint.
  3. Paste your webhook URL as the Endpoint URL and subscribe to public_incident.incident_created_v2.https://••••••••/•••••••• your personal URL, shown after install
  4. Save the endpoint. You can send a test event from its page.
  5. In incident.io, go to **Settings → Webhooks**, add an endpoint with the URL above, and subscribe to **public_incident.incident_created_v2**. Add **private_incident.incident_created_v2** too if you want a push for private incidents: incident.io only sends their ID, so that push just says a private incident was declared. Webhooks need the Team plan or higher.
  6. Incidents opened in triage (for example from an alert) push at Normal priority; declared incidents at High, or Highest when the severity is Critical (or named like SEV1/P1). Tutorial and retrospective incidents stay quiet, and so do test incidents unless you add `?test=1`:
  7. ```
  8. your webhook URL?test=1
  9. ```
  10. Don't subscribe to **public_incident.incident_updated_v2** for this: it fires on every edit. Use the **Incident status changed** recipe for progress updates.
  11. incident.io sends webhooks through Svix and signs every delivery (`webhook-id`, `webhook-timestamp`, `webhook-signature`), but JustPush doesn't check that signature yet, so keep your endpoint URL private.
The code

What runs when the webhook arrives

Studio calls handleRequest(request) with the incoming webhook and sends the message it returns. It's yours after install; change anything.

transform.js
1// incident.io → public_incident.incident_created_v2 (and private_incident.incident_created_v2)
2// Fires when an incident is declared in incident.io, or opened in triage (for example from an alert).
3// Docs: https://docs.incident.io/api-reference/incident-v2/created-public
4
5// The created events this recipe handles. Private incidents only carry the incident's ID.
6const EVENTS = {
7 "public_incident.incident_created_v2": "public",
8 "private_incident.incident_created_v2": "private",
9}
10
11// The documented example payloads use these IDs, so a test send from Settings → Webhooks
12// is recognisable (unconfirmed: that the test sends exactly these examples).
13const EXAMPLE_IDS = ["01FDAG4SAP5TYPT98WGR2N7W91", "abc123"]
14
15// Look a key up without tripping over inherited names like "toString".
16function pick(table, key, fallback) {
17 return Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback
18}
19
20// Only link to real http(s) URLs.
21function link(url, cta) {
22 return typeof url === "string" && /^https?:\/\//.test(url) ? [{ cta, url }] : []
23}
24
25// A trimmed string, or null.
26function text(value) {
27 return typeof value === "string" && value.trim() ? value.trim() : null
28}
29
30// Cut long summaries down to something that fits a notification.
31function clip(value, max) {
32 const s = text(value)
33 if (!s) return null
34 const flat = s.replace(/\s+/g, " ")
35 return flat.length > max ? `${flat.slice(0, max - 1).trimEnd()}…` : flat
36}
37
38// ?test=1 on the webhook URL also sends a quiet push for test incidents.
39function flag(request, name) {
40 return ["1", "true", "yes"].includes(String(request.query?.[name] ?? "").toLowerCase())
41}
42
43// Severity names are yours to configure; this matches incident.io's defaults (Minor, Major,
44// Critical) and common schemes like "SEV1" or "P1".
45function severityPriority(name) {
46 const s = String(name ?? "").toLowerCase()
47 if (/critical|urgent|emergency|\bsev ?[01]\b|\bp[01]\b/.test(s)) return 2 // Highest — the worst kind of incident, happening now
48 if (/minor|low|\bsev ?[3-9]\b|\bp[3-9]\b/.test(s)) return 0 // Normal — a small incident, worth knowing
49 return 1 // High — an incident was declared and needs people soon
50}
51
52function handleRequest(request) {
53 const body = request.body && typeof request.body === "object" ? request.body : {}
54
55 // incident.io sends no event header (only Svix's webhook-id/-timestamp/-signature), so
56 // the payload's event_type decides. Without it, look for the event's key in the body.
57 const type = text(body.event_type) ?? Object.keys(EVENTS).find((k) => body[k] && typeof body[k] === "object")
58 const kind = pick(EVENTS, type, null)
59 const incident = kind ? body[type] : null
60
61 // Guard: only incident-created events, so updates, follow-ups, alerts and schedule events stay quiet.
62 if (!kind || !incident || typeof incident !== "object") {
63 console.log(`Ignoring incident.io "${type ?? "unknown"}" event`)
64 return null
65 }
66
67 // A test send from Settings → Webhooks: confirm the connection quietly.
68 if (EXAMPLE_IDS.includes(incident.id)) {
69 return {
70 title: "🔔 incident.io connected",
71 message: "Webhook endpoint is working",
72 topic: "incident.io",
73 priority: -1,
74 }
75 }
76
77 // Private incidents only include the ID, so there's nothing more to show.
78 if (kind === "private") {
79 return {
80 title: "🔒 Private incident declared",
81 message: "Open incident.io to see the details",
82 topic: "incident.io",
83 priority: 1, // High — a real incident, but we can't see its severity
84 }
85 }
86
87 // Tutorials and retrospective (back-filled) incidents aren't happening now; test incidents only with ?test=1.
88 const mode = text(incident.mode) ?? "standard"
89 if (mode === "tutorial" || mode === "retrospective" || (mode === "test" && !flag(request, "test"))) {
90 console.log(`Skipping ${mode} incident`)
91 return null
92 }
93
94 // Only incidents that are open: triage, active (live) or paused. Anything else was created already over.
95 const category = text(incident.incident_status?.category) ?? "live"
96 if (!["triage", "live", "paused"].includes(category)) {
97 console.log(`Skipping incident created as "${category}"`)
98 return null
99 }
100
101 const ref = text(incident.reference)
102 const name = text(incident.name) ?? "New incident"
103 const severity = text(incident.severity?.name)
104 const status = text(incident.incident_status?.name)
105 const details = [severity, status].filter(Boolean).join(" · ")
106 const summary = clip(incident.summary, 180)
107
108 let emoji = "🚨"
109 let priority = severityPriority(severity)
110 if (category === "triage") {
111 emoji = "🔎"
112 priority = Math.min(priority, 0) // Normal — still being triaged, may not be a real incident
113 }
114 if (mode === "test") {
115 emoji = "🧪"
116 priority = -1 // Low — a test incident you asked for with ?test=1
117 }
118
119 return {
120 title: `${emoji} ${ref ? `${ref}: ` : ""}${name}`,
121 message: [details, summary].filter(Boolean).join(" — ") || "Incident declared",
122 topic: "incident.io",
123 priority,
124 buttons: [...link(incident.permalink, "Open incident"), ...link(incident.slack_channel_url, "Slack channel")],
125 }
126}
Payload

The incident.io public_incident.incident_created_v2 webhook

This is what incident.io sends to your URL for public_incident.incident_created_v2. It is a sample, trimmed to the fields recipes use.

public_incident.incident_created_v2 · sample.json
1{
2 "event_type": "public_incident.incident_created_v2",
3 "public_incident.incident_created_v2": {
4 "id": "01J8ZK4Q7N3V2XH5M6T9RB1C0D",
5 "mode": "standard",
6 "name": "Checkout is returning 500s",
7 "creator": {
8 "user": {
9 "id": "01J8ZK1A2B3C4D5E6F7G8H9J0K",
10 "name": "Sam Lee",
11 "role": "responder",
12 "email": "[email protected]"
13 }
14 },
15 "summary": "Card payments fail at the last checkout step for most customers since the 10:40 deploy.",
16 "call_url": null,
17 "severity": {
18 "id": "01J8ZJ0000SEVERITYMAJOR000",
19 "name": "Major",
20 "rank": 2,
21 "created_at": "2025-01-10T09:00:00Z",
22 "updated_at": "2025-01-10T09:00:00Z",
23 "description": "Issues causing **significant** customer impact."
24 },
25 "team_ids": [],
26 "permalink": "https://app.incident.io/incidents/482",
27 "reference": "INC-482",
28 "created_at": "2026-09-30T10:48:12.201345Z",
29 "updated_at": "2026-09-30T10:48:12.201345Z",
30 "visibility": "public",
31 "incident_type": {
32 "id": "01J8ZJ0000TYPEPRODUCTION00",
33 "name": "Production Outage",
34 "created_at": "2025-01-10T09:00:00Z",
35 "is_default": false,
36 "updated_at": "2025-01-10T09:00:00Z",
37 "description": "Customer facing production outages",
38 "create_in_triage": "optional",
39 "private_incidents_only": false
40 },
41 "slack_team_id": "T0000EXAMPLE",
42 "incident_status": {
43 "id": "01J8ZJ0000STATUSINVESTIG00",
44 "name": "Investigating",
45 "rank": 1,
46 "category": "live",
47 "created_at": "2025-01-10T09:00:00Z",
48 "updated_at": "2025-01-10T09:00:00Z",
49 "description": "We're investigating the cause of the incident."
50 },
51 "last_activity_at": "2026-09-30T10:48:12.201345Z",
52 "slack_channel_id": "C0000EXAMPLE",
53 "slack_channel_url": "https://slack.com/app_redirect?team=T0000EXAMPLE&channel=C0000EXAMPLE",
54 "slack_channel_name": "inc-482-checkout-is-returning-500s",
55 "custom_field_entries": [],
56 "incident_role_assignments": []
57 }
58}
FAQ

incident.io incident declared notifications: questions

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 incident.io 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.

Ready when you are

incident.io on your phone in two minutes.

Install the recipe, paste one URL, done. Free for 30 days, no credit card required.