{
  "name": "Review HubSpot Pipeline Issues Before Applying Fixes | Uptune",
  "nodes": [
    {
      "parameters": {
        "content": "## Review HubSpot Pipeline Issues Before Applying Fixes\nFind open deals that need attention and apply only the close dates entered by the designated reviewer.\n\nRun **Run with sample data** first. **Setup** defaults to dry run and uses the sample fixture without contacting connected apps. Configure the accounts and destinations in README.md before switching modes.",
        "width": 450,
        "height": 235
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        -390
      ],
      "id": "4e0f7c8d-5262-52ec-ad21-06a753241d3e",
      "name": "Start here"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -720,
        80
      ],
      "id": "2cb48942-4560-5dfd-99c4-f24d49ee0b74",
      "name": "Run with sample data"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nreturn [{json: {sampleRun: true, body: {\"deals\": [{\"id\": \"1042\", \"properties\": {\"dealname\": \"Northstar rollout\", \"hubspot_owner_id\": \"\", \"pipeline\": \"default\", \"dealstage\": \"qualified\", \"closedate\": \"2020-01-10\"}}, {\"id\": \"1043\", \"properties\": {\"dealname\": \"Pine Labs pilot\", \"hubspot_owner_id\": \"9\", \"pipeline\": \"default\", \"dealstage\": \"qualified\", \"closedate\": \"\"}}, {\"id\": \"1044\", \"properties\": {\"dealname\": \"Completed rollout\", \"hubspot_owner_id\": \"9\", \"pipeline\": \"default\", \"dealstage\": \"closedwon\", \"closedate\": \"2020-01-10\"}}]}}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -480,
        80
      ],
      "id": "fe60991d-5cba-5ab9-bfb2-a93f8b3e29b9",
      "name": "Sample input"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -480,
        300
      ],
      "id": "8cb3edea-25eb-5d5b-a172-0bbc23f24050",
      "name": "Receive event"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst config = {\n  \"dryRun\": true,\n  \"pipelineId\": \"default\",\n  \"openStageIds\": [\n    \"qualified\"\n  ],\n  \"reviewerEmail\": \"\",\n  \"staleDays\": 21,\n  \"updateApprovedDates\": false\n};\nreturn [{json: {...input, config}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -240,
        150
      ],
      "id": "41bc977d-744c-561e-b7ac-7a8f07510e56",
      "name": "Setup"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst preview = input.config.dryRun !== false || input.sampleRun === true;\nif (!preview && (!input.config.pipelineId || !Array.isArray(input.config.openStageIds) ||\n    !input.config.openStageIds.length || !/^[^\\s@<>,\";]+@[^\\s@<>,\";]+\\.[^\\s@<>,\";]+$/.test(input.config.reviewerEmail) ||\n    !Number.isInteger(input.config.staleDays) || input.config.staleDays < 1)) {\n  throw new Error('Set the pipeline, open stage IDs, reviewer email and stale-days threshold.');\n}\nreturn [{json:{preview, sample: input.body || {\"deals\": [{\"id\": \"1042\", \"properties\": {\"dealname\": \"Northstar rollout\", \"hubspot_owner_id\": \"\", \"pipeline\": \"default\", \"dealstage\": \"qualified\", \"closedate\": \"2020-01-10\"}}, {\"id\": \"1043\", \"properties\": {\"dealname\": \"Pine Labs pilot\", \"hubspot_owner_id\": \"9\", \"pipeline\": \"default\", \"dealstage\": \"qualified\", \"closedate\": \"\"}}, {\"id\": \"1044\", \"properties\": {\"dealname\": \"Completed rollout\", \"hubspot_owner_id\": \"9\", \"pipeline\": \"default\", \"dealstage\": \"closedwon\", \"closedate\": \"2020-01-10\"}}]}}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -75,
        150
      ],
      "id": "dbe01792-9d36-5f2a-9bab-3d3c2e13c419",
      "name": "Select run mode"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "eb5d7094-e2ac-55ed-ad03-81eeaab297fe",
              "leftValue": "={{ !$json.preview }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        160,
        150
      ],
      "id": "1a137702-629d-5e7a-b4f5-95cbeafa3c08",
      "name": "Read live deals?"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/deals/search",
        "authentication": "genericCredentialType",
        "options": {
          "timeout": 30000
        },
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({limit:100,filterGroups:[{filters:[{propertyName:'pipeline',operator:'EQ',value:$(\"Setup\").first().json.config.pipelineId},{propertyName:'dealstage',operator:'IN',values:$(\"Setup\").first().json.config.openStageIds}]}],properties:['dealname','dealstage','pipeline','hubspot_owner_id','closedate','hs_lastmodifieddate']}) }}"
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        395,
        150
      ],
      "id": "43968f14-b3a3-57b0-a27c-2d0076fa37c0",
      "name": "Search open HubSpot deals"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst config = $(\"Setup\").first().json.config;\nconst preview = $(\"Select run mode\").first().json.preview;\nif (!preview && (input.paging?.next || input.total > 100)) throw new Error('More than 100 deals match. Narrow openStageIds to keep the review complete.');\nconst deals = preview ? $(\"Select run mode\").first().json.sample.deals : input.results;\nif (!Array.isArray(deals)) throw new Error('HubSpot did not return a deal list.');\nconst today = new Date().toISOString().slice(0,10);\nconst escape = value => String(value).replace(/[&<>\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]));\nconst dateKey = value => {\n  if (!value) return '';\n  const text = String(value);\n  const timestamp = /^\\d{11,}$/.test(text) ? Number(text) : text;\n  const date = new Date(timestamp);\n  return isNaN(date) ? '' : date.toISOString().slice(0,10);\n};\nconst findings = [];\nfor (const deal of deals) {\n  const p = deal.properties || {};\n  if (!/^\\d+$/.test(String(deal.id)) || p.pipeline !== config.pipelineId || !config.openStageIds.includes(p.dealstage)) continue;\n  const closeDate = dateKey(p.closedate);\n  const canUpdate = !closeDate || closeDate < today;\n  const lastChanged = new Date(p.hs_lastmodifieddate || '').getTime();\n  const stale = Number.isFinite(lastChanged) && Date.now() - lastChanged > config.staleDays * 86400000;\n  const reasons = [!closeDate ? 'Missing close date' : closeDate < today ? 'Past close date' : '', !p.hubspot_owner_id ? 'Missing owner' : '', stale ? 'Deal record has not been updated recently' : ''].filter(Boolean);\n  if (reasons.length) findings.push({id:String(deal.id), name:p.dealname || 'Untitled deal', reasons, canUpdate,\n    snapshot:{closedate:p.closedate || '', pipeline:p.pipeline, dealstage:p.dealstage}});\n}\nconst lines = findings.map(row => '<li>' + escape(row.id + ' · ' + row.name + ' · ' + row.reasons.join(', ')) + '</li>');\nconst canReview = config.updateApprovedDates === true && findings.some(row => row.canUpdate);\nreturn [{json:{preview, canReview, findings, subject:'HubSpot pipeline review · ' + findings.length + ' flagged deals',\n  emailHtml:'<p>Review these open deals in HubSpot.</p><ul>' + lines.join('') + '</ul>' +\n  (canReview ? '<p>To approve a close date, enter one deal ID and date per line in the form, using DEAL_ID=YYYY-MM-DD.</p>' : '<p>Review and assign the follow-up in HubSpot.</p>')}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        630,
        150
      ],
      "id": "c69c59b1-fe21-5f29-ad61-df81f5d5648e",
      "name": "Build review report"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "20f4ae0e-937f-5aa8-9a79-351ffc624287",
              "leftValue": "={{ !$json.preview && $json.findings.length > 0 }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        865,
        150
      ],
      "id": "4062b06a-f71b-5597-b552-3df44345bb1d",
      "name": "Notify reviewer?"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "1910ef88-4c9e-54ca-a6c3-6d299dc50fbb",
              "leftValue": "={{ $json.canReview === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1100,
        150
      ],
      "id": "ec4c17ab-f162-54b9-96b4-9c6c1a498c75",
      "name": "Request date approval?"
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "sendTo": "={{ $(\"Setup\").first().json.config.reviewerEmail }}",
        "subject": "={{ $json.subject }}",
        "emailType": "html",
        "message": "={{ $json.emailHtml }}",
        "options": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        1335,
        150
      ],
      "id": "5004586d-9c36-579d-aa3e-64f3a7503f8d",
      "name": "Email report only"
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "sendAndWait",
        "sendTo": "={{ $(\"Setup\").first().json.config.reviewerEmail }}",
        "subject": "={{ $json.subject }}",
        "message": "={{ $json.emailHtml }}",
        "responseType": "customForm",
        "formFields": {
          "values": [
            {
              "fieldType": "textarea",
              "fieldLabel": "Approved close dates"
            }
          ]
        },
        "options": {
          "limitWaitTime": {
            "values": {
              "limitType": "afterTimeInterval",
              "resumeUnit": "days",
              "resumeAmount": 2
            }
          },
          "appendAttribution": false,
          "responseFormTitle": "Approve HubSpot close dates",
          "responseFormButtonLabel": "Submit approved dates"
        }
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        1570,
        150
      ],
      "id": "e8a836ca-5007-51d3-892b-08b29f5bf5ff",
      "name": "Request approved dates"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst raw = input.data?.['Approved close dates'];\nconst report = $(\"Build review report\").first().json;\nif (typeof raw !== 'string' || !raw.trim()) return [{json:{updates:[],outcome:'no_approved_updates'}}];\nconst allowed = new Map(report.findings.filter(row => row.canUpdate).map(row => [row.id,row]));\nconst updates = new Map();\nconst now = new Date();\nconst today = now.toISOString().slice(0,10);\nconst latest = new Date(now.getTime() + 366 * 86400000).toISOString().slice(0,10);\nfor (const line of raw.trim().split(/\\r?\\n/).filter(line => line.trim())) {\n  const match = line.trim().match(/^(\\d+)\\s*=\\s*(\\d{4}-\\d{2}-\\d{2})$/);\n  if (!match || !allowed.has(match[1]) || updates.has(match[1])) throw new Error('Use one eligible deal ID per line as DEAL_ID=YYYY-MM-DD.');\n  const date = new Date(match[2] + 'T00:00:00Z');\n  if (isNaN(date) || date.toISOString().slice(0,10) !== match[2] || match[2] < today || match[2] > latest) throw new Error('Choose a real close date between today and one year ahead.');\n  updates.set(match[1], {id:match[1], closeDate:date.toISOString(), snapshot:allowed.get(match[1]).snapshot});\n}\nreturn [{json:{updates:[...updates.values()]}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1805,
        150
      ],
      "id": "c0e0912f-9307-5430-ac0f-c220e08889a8",
      "name": "Validate approved dates"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "ed14e864-4d77-5bf2-b884-202e849ce03a",
              "leftValue": "={{ $json.updates.length > 0 }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2040,
        150
      ],
      "id": "c27b2ec8-c487-504d-952f-281c41a982ec",
      "name": "Any approved dates?"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/deals/batch/read",
        "authentication": "genericCredentialType",
        "options": {
          "timeout": 30000
        },
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({properties:['pipeline','dealstage','closedate'],inputs:$(\"Validate approved dates\").first().json.updates.map(update=>({id:update.id}))}) }}"
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2275,
        150
      ],
      "id": "5966b80b-cde7-5bd7-aea4-d8b232f386a3",
      "name": "Read approved deals again"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nif (!Array.isArray(input.results)) throw new Error('HubSpot did not return the deal snapshots.');\nconst current = new Map(input.results.map(deal => [String(deal.id),deal.properties || {}]));\nconst ready = [];\nconst skipped = [];\nfor (const update of $(\"Validate approved dates\").first().json.updates) {\n  const now = current.get(update.id);\n  if (!now || Object.entries(update.snapshot).some(([key, value]) => (now[key] || '') !== value)) {\n    skipped.push(update.id);\n  } else {\n    ready.push({id:update.id,properties:{closedate:update.closeDate}});\n  }\n}\nreturn [{json:{ready,skipped}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2510,
        150
      ],
      "id": "48dc1acc-a1dd-5163-81ac-8e255e4b1975",
      "name": "Discard changed deals"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "675a1e10-0844-5526-89ee-7095e833b629",
              "leftValue": "={{ $json.ready.length > 0 }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2745,
        150
      ],
      "id": "905d91cc-9e90-513a-873d-33f76242f768",
      "name": "Any unchanged deals?"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/deals/batch/update",
        "authentication": "genericCredentialType",
        "options": {
          "timeout": 30000
        },
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({inputs:$json.ready}) }}"
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2980,
        150
      ],
      "id": "b036a4c6-02ec-52b9-a178-8596e9e9b3a9",
      "name": "Apply approved close dates"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst plan = $(\"Discard changed deals\").first().json;\nconst returned = new Set((input.results || []).map(row => String(row.id)));\nif (input.status !== 'COMPLETE' || input.errors?.length || plan.ready.some(row => !returned.has(row.id))) {\n  throw new Error('HubSpot did not confirm every update. Inspect the batch response and deal records before retrying.');\n}\nreturn [{json:{updated:plan.ready.map(row=>row.id),skipped:plan.skipped}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3215,
        150
      ],
      "id": "084a1b82-dfd0-5b19-b1bc-0852915b136a",
      "name": "Confirm HubSpot results"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nreturn [{json:{updated:[],skipped:input.skipped}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3450,
        150
      ],
      "id": "7b0fcf83-351e-54d3-b841-e7e9523491af",
      "name": "Report skipped deals"
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "sendTo": "={{ $(\"Setup\").first().json.config.reviewerEmail }}",
        "subject": "HubSpot pipeline review results",
        "message": "={{ 'Updated deal IDs: ' + ($json.updated.join(', ') || 'none') + '\\nSkipped because the deal changed or could not be found: ' + ($json.skipped.join(', ') || 'none') }}",
        "options": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        3685,
        150
      ],
      "id": "67667888-5ac3-5b2b-8371-4dadd6ea06c4",
      "name": "Email review results"
    }
  ],
  "connections": {
    "Run with sample data": {
      "main": [
        [
          {
            "node": "Sample input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample input": {
      "main": [
        [
          {
            "node": "Setup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive event": {
      "main": [
        [
          {
            "node": "Setup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Setup": {
      "main": [
        [
          {
            "node": "Select run mode",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select run mode": {
      "main": [
        [
          {
            "node": "Read live deals?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read live deals?": {
      "main": [
        [
          {
            "node": "Search open HubSpot deals",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build review report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search open HubSpot deals": {
      "main": [
        [
          {
            "node": "Build review report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build review report": {
      "main": [
        [
          {
            "node": "Notify reviewer?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify reviewer?": {
      "main": [
        [
          {
            "node": "Request date approval?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request date approval?": {
      "main": [
        [
          {
            "node": "Request approved dates",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email report only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request approved dates": {
      "main": [
        [
          {
            "node": "Validate approved dates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate approved dates": {
      "main": [
        [
          {
            "node": "Any approved dates?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any approved dates?": {
      "main": [
        [
          {
            "node": "Read approved deals again",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read approved deals again": {
      "main": [
        [
          {
            "node": "Discard changed deals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Discard changed deals": {
      "main": [
        [
          {
            "node": "Any unchanged deals?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any unchanged deals?": {
      "main": [
        [
          {
            "node": "Apply approved close dates",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Report skipped deals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apply approved close dates": {
      "main": [
        [
          {
            "node": "Confirm HubSpot results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Confirm HubSpot results": {
      "main": [
        [
          {
            "node": "Email review results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Report skipped deals": {
      "main": [
        [
          {
            "node": "Email review results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveDataSuccessExecution": "none",
    "saveDataErrorExecution": "all"
  },
  "pinData": {},
  "tags": []
}
