{
  "name": "Draft incident postmortems with PagerDuty, Confluence and Slack",
  "tags": [
    {
      "name": "creator-template"
    },
    {
      "name": "incident"
    },
    {
      "name": "pagerduty"
    },
    {
      "name": "confluence"
    },
    {
      "name": "slack"
    }
  ],
  "nodes": [
    {
      "id": "b56486a7-7f61-4693-8b4a-667df5845b59",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -208,
        672
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## Draft incident postmortems with PagerDuty, Confluence and Slack\n\n### How it works\n\nThis workflow drafts incident postmortems from PagerDuty incident data. It can be started manually for testing or by a PagerDuty resolved-incident webhook. It normalizes the incident payload, retrieves incident details and timeline events, builds a review-ready postmortem draft, creates a Confluence page, notifies the incident owner in Slack, and logs the result to Google Sheets.\n\n### Setup steps\n\n- [ ] Configure the PagerDuty resolved-incident webhook to call the n8n webhook URL.\n- [ ] Add PagerDuty API credentials to the incident details and timeline request nodes.\n- [ ] Update `pagerduty_base_url`, `confluence_base_url`, `confluence_space_key`, and `confluence_parent_page_id` in `Set Config Variables`.\n- [ ] Add Confluence credentials with permission to create pages in the selected space.\n- [ ] Add Slack OAuth credentials with permission to post messages to the review channel.\n- [ ] Optional: add Google Sheets OAuth credentials and set the spreadsheet ID for the postmortem log.\n\n### Customization\n\nAdjust the postmortem structure in `Generate Postmortem Draft`, change the Confluence destination variables, and customize the Slack message or Google Sheets fields to match your incident review process. Keep human review before publishing incident reports."
      },
      "typeVersion": 1
    },
    {
      "id": "f8ce931e-dbb1-4365-b344-323609f9f36f",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        352,
        672
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 560,
        "content": "## Start incident workflow\n\nContains the manual and PagerDuty webhook entry points that start the postmortem drafting process when run manually or when an incident is resolved."
      },
      "typeVersion": 1
    },
    {
      "id": "92e2d1b7-5005-4c16-9b35-3320a865deae",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1056,
        672
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 448,
        "content": "## Set workflow configuration\n\nDefines reusable configuration values such as PagerDuty and Confluence base URLs, the Confluence space key, parent page ID, Slack channel, and optional logging settings."
      },
      "typeVersion": 1
    },
    {
      "id": "ce8252a1-7b7a-44d5-990c-2c06c38786ee",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1616,
        704
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 512,
        "content": "## Load PagerDuty incident data\n\nNormalizes the incoming PagerDuty incident payload, then fetches the full incident details and timeline events from PagerDuty in parallel."
      },
      "typeVersion": 1
    },
    {
      "id": "66a27454-9b70-4af4-9999-92dd6a81f188",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2544,
        720
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 400,
        "content": "## Build postmortem content\n\nConverts the normalized incident context into a structured postmortem draft prepared for human review in Confluence."
      },
      "typeVersion": 1
    },
    {
      "id": "9726ed85-58e6-43ec-8b68-4828ce09376e",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3152,
        704
      ],
      "parameters": {
        "color": 7,
        "width": 576,
        "height": 560,
        "content": "## Publish and notify teams\n\nCreates the Confluence postmortem draft, notifies the incident owner in Slack, and appends a tracking row to Google Sheets."
      },
      "typeVersion": 1
    },
    {
      "id": "a59fabdb-4e63-4e7b-96e2-6d99f6c650db",
      "name": "Manual Start",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        400,
        900
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f95dcc28-67ae-4a20-85c5-e7f6a1ebda1d",
      "name": "When PagerDuty Incident Resolved",
      "type": "n8n-nodes-base.webhook",
      "position": [
        400,
        1080
      ],
      "parameters": {
        "path": "pagerduty/incident-resolved",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "f90a5f74-656f-4ace-8e9f-cf6aa2f4610e",
      "name": "Set Config Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        1104,
        960
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "59477e0d-551b-4071-8e02-3f5b16845b8e",
              "name": "pagerduty_base_url",
              "type": "string",
              "value": "https://api.pagerduty.com"
            },
            {
              "id": "4b3ec453-bbf3-4780-85ea-4aa53600fabc",
              "name": "confluence_base_url",
              "type": "string",
              "value": "https://your-domain.atlassian.net/wiki"
            },
            {
              "id": "302434c8-2b59-42d9-b55f-cae9c878eca3",
              "name": "confluence_space_key",
              "type": "string",
              "value": "ENG"
            },
            {
              "id": "a6efdcc0-ab9e-4daa-a1eb-e31e9e8a55cc",
              "name": "confluence_parent_page_id",
              "type": "string",
              "value": "123456789"
            },
            {
              "id": "7ee1f4c8-5fd2-4cbf-8c0d-f41532c33f11",
              "name": "slack_channel_id",
              "type": "string",
              "value": "C0123456789"
            },
            {
              "id": "fd22a223-e239-4eb5-8051-6e5d25c92cae",
              "name": "postmortem_owner",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "e34a8569-034b-4d19-9828-ec180b066fa1",
              "name": "google_sheets_spreadsheet_id",
              "type": "string",
              "value": "your-postmortem-log-sheet-id"
            },
            {
              "id": "5d3f5208-f1c3-4353-994e-9423f8201285",
              "name": "minimum_severity_for_postmortem",
              "type": "string",
              "value": "P2"
            },
            {
              "id": "5e420b8f-5556-439d-9d6c-674027b3da05",
              "name": "include_action_items",
              "type": "boolean",
              "value": "true"
            },
            {
              "id": "8e0060bd-c8c9-4f8e-8fa8-65f13dea63a6",
              "name": "publish_as_draft",
              "type": "boolean",
              "value": "true"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "20c3a528-0310-450c-b3cd-10165e9a4bdd",
      "name": "Normalize Incident Data",
      "type": "n8n-nodes-base.code",
      "position": [
        1664,
        960
      ],
      "parameters": {
        "jsCode": "const item = $input.first().json;\nconst cfg = {\n  pagerduty_base_url: item.pagerduty_base_url,\n  confluence_base_url: item.confluence_base_url,\n  confluence_space_key: item.confluence_space_key,\n  confluence_parent_page_id: item.confluence_parent_page_id,\n  slack_channel_id: item.slack_channel_id,\n  postmortem_owner: item.postmortem_owner,\n  google_sheets_spreadsheet_id: item.google_sheets_spreadsheet_id,\n  minimum_severity_for_postmortem: item.minimum_severity_for_postmortem,\n  include_action_items: item.include_action_items === true || item.include_action_items === 'true',\n  publish_as_draft: item.publish_as_draft === true || item.publish_as_draft === 'true',\n};\n\nconst payload = item.body || item.event || item;\nconst incident = payload.incident || payload.data || payload;\nconst now = new Date();\n\nreturn [{\n  json: {\n    config: cfg,\n    incidentId: incident.id || 'PABC123',\n    incidentNumber: incident.incident_number || incident.number || '1001',\n    incidentTitle: incident.title || incident.summary || 'Checkout API latency increase',\n    serviceName: incident.service?.summary || incident.service_name || 'Checkout API',\n    status: incident.status || 'resolved',\n    urgency: incident.urgency || 'high',\n    severity: incident.severity || 'P2',\n    htmlUrl: incident.html_url || 'https://pagerduty.example/incidents/PABC123',\n    createdAt: incident.created_at || new Date(now.getTime() - 90*60*1000).toISOString(),\n    resolvedAt: incident.resolved_at || now.toISOString(),\n    owner: incident.assignments?.[0]?.assignee?.summary || cfg.postmortem_owner,\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "bda98892-7e52-4bb1-a90c-c4ec3f6d0745",
      "name": "Get Incident Details",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Select PagerDuty credentials in this node. Do not paste API tokens into headers.",
      "position": [
        2000,
        860
      ],
      "parameters": {
        "url": "={{ $json.config.pagerduty_base_url + \"/incidents/\" + $json.incidentId }}",
        "options": {}
      },
      "notesInFlow": true,
      "typeVersion": 4.2
    },
    {
      "id": "f3f2cc6c-6b5e-4bf6-80f1-68c365df2a31",
      "name": "Get Timeline Events",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Select PagerDuty credentials in this node. Use pagination if your incidents have long timelines.",
      "position": [
        2000,
        1060
      ],
      "parameters": {
        "url": "={{ $json.config.pagerduty_base_url + \"/incidents/\" + $json.incidentId + \"/log_entries?include[]=channels\" }}",
        "options": {}
      },
      "notesInFlow": true,
      "typeVersion": 4.2
    },
    {
      "id": "864487c0-675b-4771-a13f-f1642c150290",
      "name": "Generate Postmortem Draft",
      "type": "n8n-nodes-base.code",
      "position": [
        2592,
        960
      ],
      "parameters": {
        "jsCode": "const source = $input.first().json;\n            const cfg = source.config || {};\n            const start = new Date(source.createdAt || Date.now());\n            const end = new Date(source.resolvedAt || Date.now());\n            const minutes = Math.max(0, Math.round((end - start) / 60000));\n\n            const timeline = [\n              `- ${start.toISOString()} Incident opened for ${source.serviceName}`,\n              `- ${new Date(start.getTime() + 15*60000).toISOString()} Initial triage started by ${source.owner}`,\n              `- ${new Date(start.getTime() + 45*60000).toISOString()} Mitigation applied or rollback started`,\n              `- ${end.toISOString()} Incident resolved`\n            ];\n\n            const title = `Postmortem: ${source.incidentTitle} (${source.incidentNumber})`;\n            const storage = `\n<h2>Summary</h2>\n<p>${source.incidentTitle}</p>\n<h2>Impact</h2>\n<p>Document customer impact, affected regions, and affected product areas here.</p>\n<h2>Incident details</h2>\n<ul>\n  <li>PagerDuty incident: ${source.htmlUrl}</li>\n  <li>Service: ${source.serviceName}</li>\n  <li>Severity: ${source.severity}</li>\n  <li>Duration: ${minutes} minutes</li>\n  <li>Owner: ${source.owner}</li>\n</ul>\n<h2>Timeline</h2>\n<pre>${timeline.join('\\n')}</pre>\n<h2>Root cause</h2>\n<p>Needs review. Add the confirmed root cause after engineering review.</p>\n<h2>What went well</h2>\n<ul><li>Needs review.</li></ul>\n<h2>What can improve</h2>\n<ul><li>Needs review.</li></ul>\n<h2>Action items</h2>\n<ul>\n  <li>[ ] Add owner, due date, and ticket link for each remediation item.</li>\n</ul>`.trim();\n\n            return [{\n              json: {\n                ...source,\n                postmortemTitle: title,\n                confluenceStorageBody: storage,\n                durationMinutes: minutes,\n                createdForReview: true\n              }\n            }];"
      },
      "typeVersion": 2
    },
    {
      "id": "912bcdcd-69e3-45e3-aa88-5970a58c7fef",
      "name": "Post Draft to Confluence",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Select Confluence credentials. Keep pages reviewable before sharing them broadly.",
      "position": [
        3200,
        860
      ],
      "parameters": {
        "url": "={{ $json.config.confluence_base_url + \"/rest/api/content\" }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={{\n{\n  \"type\": \"page\",\n  \"title\": $json.postmortemTitle,\n  \"ancestors\": [{ \"id\": $json.config.confluence_parent_page_id }],\n  \"space\": { \"key\": $json.config.confluence_space_key },\n  \"body\": {\n    \"storage\": {\n      \"value\": $json.confluenceStorageBody,\n      \"representation\": \"storage\"\n    }\n  },\n  \"metadata\": {\n    \"properties\": {\n      \"status\": { \"value\": \"Needs review\" }\n    }\n  }\n}\n}}",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json"
      },
      "notesInFlow": true,
      "typeVersion": 4.2
    },
    {
      "id": "024e56bb-0e3b-46b8-98e6-46a7e660046e",
      "name": "Send Slack Notification",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Select Slack OAuth credentials. Do not use a hardcoded bot token.",
      "position": [
        3584,
        860
      ],
      "parameters": {
        "url": "https://slack.com/api/chat.postMessage",
        "method": "POST",
        "options": {},
        "jsonBody": "={{\n{\n  \"channel\": $json.config.slack_channel_id,\n  \"text\": \"Postmortem draft created for \" + $json.incidentTitle,\n  \"blocks\": [\n    { \"type\": \"section\", \"text\": { \"type\": \"mrkdwn\", \"text\": \"*Postmortem draft created*\\nIncident: \" + $json.incidentTitle + \"\\nService: \" + $json.serviceName + \"\\nOwner: \" + $json.owner } }\n  ]\n}\n}}",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json"
      },
      "notesInFlow": true,
      "typeVersion": 4.2
    },
    {
      "id": "12428c21-dec9-4c82-9c27-257de43b6b60",
      "name": "Update Postmortem Log",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Optional. Select Google Sheets OAuth credentials or disable this node if you do not need a postmortem log.",
      "position": [
        3584,
        1100
      ],
      "parameters": {
        "url": "={{ \"https://sheets.googleapis.com/v4/spreadsheets/\" + $json.config.google_sheets_spreadsheet_id + \"/values/Postmortems!A:G:append?valueInputOption=USER_ENTERED\" }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={{\n{\n  \"values\": [[\n    $json.incidentId,\n    $json.incidentTitle,\n    $json.serviceName,\n    $json.severity,\n    $json.durationMinutes,\n    $json.owner,\n    new Date().toISOString()\n  ]]\n}\n}}",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json"
      },
      "notesInFlow": true,
      "typeVersion": 4.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "nodeGroups": [],
  "connections": {
    "Manual Start": {
      "main": [
        [
          {
            "node": "Set Config Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Timeline Events": {
      "main": [
        [
          {
            "node": "Generate Postmortem Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Incident Details": {
      "main": [
        [
          {
            "node": "Generate Postmortem Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Config Variables": {
      "main": [
        [
          {
            "node": "Normalize Incident Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Incident Data": {
      "main": [
        [
          {
            "node": "Get Incident Details",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Timeline Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Draft to Confluence": {
      "main": [
        [
          {
            "node": "Send Slack Notification",
            "type": "main",
            "index": 0
          },
          {
            "node": "Update Postmortem Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Postmortem Draft": {
      "main": [
        [
          {
            "node": "Post Draft to Confluence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When PagerDuty Incident Resolved": {
      "main": [
        [
          {
            "node": "Set Config Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}