{
  "id": "FpauF9jfmoRaFe8q",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Review & Reputation Manager",
  "tags": [],
  "nodes": [
    {
      "id": "cbfbcc8a-5690-46ac-9c91-a681f199018f",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        672
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 1488,
        "content": "## \u2b50 AI Review & Reputation Manager\n\nPolls Google Business and Trustpilot hourly, deduplicates new reviews, drafts LLM-powered replies on-brand, and routes positive reviews for one-tap approval while escalating negative reviews to the owner with suggested resolutions.\n\n**Perfect for:** local businesses, service providers, and ecommerce companies managing online reputation at scale.\n\n---\n\n## How it works\n\n1. **Every Hour** \u2014 Schedule trigger runs hourly.\n2. **Fetch Google Reviews** \u2014 HTTP request retrieves reviews from Google Business Profile API.\n3. **Fetch Trustpilot Reviews** \u2014 HTTP request retrieves reviews from Trustpilot API.\n4. **Combine Sources** \u2014 Merge node combines Google and Trustpilot results.\n5. **Dedupe New Reviews** \u2014 Code node filters out previously seen reviews using workflow static data.\n6. **Any New Reviews?** \u2014 If condition: exits if no fresh reviews, otherwise continues.\n7. **No New Reviews** \u2014 No-op endpoint when no new reviews exist.\n8. **Process Each Review** \u2014 SplitInBatches loops through each fresh review one at a time.\n9. **Draft Reply (AI Agent)** \u2014 LangChain agent with OpenAI LLM drafts sentiment-aware reply.\n10. **OpenAI Chat Model** \u2014 Language model node (gpt-5-mini) powers the AI agent.\n11. **Structured Output Parser** \u2014 Validates LLM output as JSON (sentiment, draftReply, suggestedResolution).\n12. **Negative Review?** \u2014 If condition: branches on sentiment.\n13. **Escalate to Owner** \u2014 Slack node alerts owner for negative reviews with suggested resolution.\n14. **Request Approval** \u2014 Slack node sends positive review drafts for human approval, waits 24 hours max.\n15. **Approved?** \u2014 If condition: publishes only if approved.\n16. **Publish Reply** \u2014 HTTP request submits approved reply to Trustpilot API.\n17. **Error Trigger** \u2014 Error handler catches workflow failures.\n18. **Notify Ops** \u2014 Slack node alerts ops team of errors.\n\n---\n\n## Setup (about 20 minutes)\n\n1. **Google Business Profile API** \u2014 Add header auth credentials in *Fetch Google Reviews* with your account and location IDs.\n2. **Trustpilot API** \u2014 Add header auth credentials in *Fetch Trustpilot Reviews* and *Publish Reply* with your business unit ID.\n3. **OpenAI Account** \u2014 Connect OpenAI API in *OpenAI Chat Model*. Model is set to gpt-5-mini with temperature 0.4.\n4. **Slack Account** \u2014 Connect Slack OAuth in *Escalate to Owner*, *Request Approval*, and *Notify Ops*. Set your owner-alerts and review-replies channels.\n> Adjust the sentiment classification logic in *Draft Reply (AI Agent)* system message if you want custom routing rules. The workflow deduplicates by tracking review IDs in static data; this persists across runs but does not survive instance restarts."
      },
      "typeVersion": 1
    },
    {
      "id": "3c757dfb-b6f9-47e5-96bc-b37e189daa4c",
      "name": "1\ufe0f\u20e3 Fetch & Dedupe",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        544
      ],
      "parameters": {
        "color": 5,
        "width": 1064,
        "height": 728,
        "content": "## 1\ufe0f\u20e3 Fetch & Dedupe\n\nThe **Every Hour** schedule trigger starts the run. The **Fetch Google Reviews** and **Fetch Trustpilot Reviews** nodes retrieve reviews in parallel. The **Combine Sources** node merges both lists. The **Dedupe New Reviews** code node filters to only unseen reviews using static data tracking, and **Any New Reviews?** exits early if nothing is new."
      },
      "typeVersion": 1
    },
    {
      "id": "63edbdcc-df3d-400b-9d66-a868a8338384",
      "name": "2\ufe0f\u20e3 AI Draft & Route",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2096,
        560
      ],
      "parameters": {
        "color": 3,
        "width": 712,
        "height": 864,
        "content": "## 2\ufe0f\u20e3 AI Draft & Route\n\nThe **Process Each Review** node loops through each fresh review. The **Draft Reply (AI Agent)** LangChain agent calls the **OpenAI Chat Model** and validates output via the **Structured Output Parser**, extracting sentiment, draft reply, and suggested resolution as JSON. The **Negative Review?** condition branches: negative reviews go to owner escalation, positive reviews continue to approval."
      },
      "typeVersion": 1
    },
    {
      "id": "e547470b-de1e-4c38-89ba-4ba10f744445",
      "name": "3\ufe0f\u20e3 Approve & Publish",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2848,
        656
      ],
      "parameters": {
        "color": 6,
        "width": 776,
        "height": 816,
        "content": "## 3\ufe0f\u20e3 Approve & Publish\n\nThe **Escalate to Owner** node immediately posts negative reviews and resolutions to the owner Slack channel for manual action. The **Request Approval** node sends positive draft replies to the review-replies channel with a 24-hour timeout for approval. The **Approved?** condition checks the Slack response. If approved, the **Publish Reply** node posts to Trustpilot. Both branches loop back to **Process Each Review**."
      },
      "typeVersion": 1
    },
    {
      "id": "ee4e67ff-cc13-4361-80ca-46c1e03b8b68",
      "name": "4\ufe0f\u20e3 Monitor",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        1312
      ],
      "parameters": {
        "color": 4,
        "width": 676,
        "height": 340,
        "content": "## 4\ufe0f\u20e3 Monitor\n\nThe **Error Trigger** catches any workflow failure. The **Notify Ops** node immediately alerts the ops team via Slack with error details for rapid troubleshooting."
      },
      "typeVersion": 1
    },
    {
      "id": "d2f3e026-b013-4d44-be16-ae68860ef666",
      "name": "Every Hour",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1024,
        960
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ef24142b-7bb5-4b42-9309-6706354529d4",
      "name": "Fetch Google Reviews",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        1264,
        880
      ],
      "parameters": {
        "url": "https://mybusiness.googleapis.com/v4/YOUR_ACCOUNT/locations/YOUR_LOCATION/reviews",
        "options": {
          "timeout": 10000
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "3e21a425-4eae-4ce2-bb6d-8a339c158ab6",
      "name": "Fetch Trustpilot Reviews",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        1264,
        1056
      ],
      "parameters": {
        "url": "https://api.trustpilot.com/v1/business-units/YOUR_UNIT_ID/reviews",
        "options": {
          "timeout": 10000
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "dd80613d-0068-43db-953d-21f73dd73793",
      "name": "Combine Sources",
      "type": "n8n-nodes-base.merge",
      "position": [
        1504,
        960
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "66a57c46-e32c-4c2c-9ace-4c8ba4528873",
      "name": "Dedupe New Reviews",
      "type": "n8n-nodes-base.code",
      "position": [
        1728,
        960
      ],
      "parameters": {
        "jsCode": "const staticData = $getWorkflowStaticData('global');\nif (!staticData.seenIds) staticData.seenIds = [];\nconst items = $input.all();\nconst fresh = items.filter(i => {\n  const id = i.json.reviewId || i.json.id;\n  return id && !staticData.seenIds.includes(id);\n});\nfresh.forEach(i => staticData.seenIds.push(i.json.reviewId || i.json.id));\nif (staticData.seenIds.length > 5000) staticData.seenIds = staticData.seenIds.slice(-2000);\nreturn fresh;"
      },
      "typeVersion": 2
    },
    {
      "id": "4b7c90b5-a863-4b9d-8300-b64464b47037",
      "name": "Any New Reviews?",
      "type": "n8n-nodes-base.if",
      "position": [
        1952,
        960
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-new-reviews",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $input.all().length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e8479ec0-8114-429f-8aa1-e710437aebb0",
      "name": "No New Reviews",
      "type": "n8n-nodes-base.noOp",
      "position": [
        2192,
        816
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f8503e41-5584-4326-9fe4-2b5be64f6ec6",
      "name": "Process Each Review",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        2192,
        1040
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "a005edba-2192-4e85-a83f-5344a6609d79",
      "name": "Limit To Done",
      "type": "n8n-nodes-base.noOp",
      "position": [
        2432,
        816
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "ac875b64-f44b-4766-b7a7-742b0ea0cbae",
      "name": "Draft Reply (AI Agent)",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2432,
        1040
      ],
      "parameters": {
        "text": "=Review ({{ $json.rating }} stars) from {{ $json.author }}: \"{{ $json.text }}\"",
        "options": {
          "systemMessage": "You are the reputation manager for a local business. For every review, output JSON with: sentiment ('positive' or 'negative'), draftReply (a warm, on-brand, concise reply, max 60 words), and suggestedResolution (empty string if positive, otherwise a concrete next step for the owner)."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "832d53f2-852b-4a4b-b411-084ac8357509",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2416,
        1216
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini"
        },
        "options": {
          "temperature": 0.4
        },
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "14bf9b41-3979-494a-9ca0-ba7b107e5994",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        2576,
        1216
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"sentiment\": { \"type\": \"string\", \"enum\": [\"positive\", \"negative\"] },\n    \"draftReply\": { \"type\": \"string\" },\n    \"suggestedResolution\": { \"type\": \"string\" }\n  },\n  \"required\": [\"sentiment\", \"draftReply\"]\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "202dba62-2050-4915-8a42-29fe3680c9af",
      "name": "Negative Review?",
      "type": "n8n-nodes-base.if",
      "position": [
        2672,
        1040
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-negative",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.output.sentiment }}",
              "rightValue": "negative"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4154ca01-2ecb-49b4-a9ec-03df585d3263",
      "name": "Escalate to Owner",
      "type": "n8n-nodes-base.slack",
      "position": [
        2912,
        896
      ],
      "parameters": {
        "text": "=\ud83d\udd34 Negative review ({{ $('Process Each Review').item.json.rating }}\u2605) from {{ $('Process Each Review').item.json.author }}: \"{{ $('Process Each Review').item.json.text }}\"\\nSuggested resolution: {{ $json.output.suggestedResolution }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "REPLACE_WITH_CHANNEL_ID",
          "cachedResultName": "owner-alerts"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.3
    },
    {
      "id": "10017f38-9917-42df-b881-2667c17c9570",
      "name": "Request Approval",
      "type": "n8n-nodes-base.slack",
      "position": [
        2912,
        1152
      ],
      "parameters": {
        "select": "channel",
        "message": "=Draft reply for {{ $('Process Each Review').item.json.author }}'s {{ $('Process Each Review').item.json.rating }}\u2605 review:\\n\\n\"{{ $json.output.draftReply }}\"",
        "options": {
          "limitWaitTime": {
            "values": {
              "resumeAmount": 24
            }
          }
        },
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "REPLACE_WITH_CHANNEL_ID",
          "cachedResultName": "review-replies"
        },
        "operation": "sendAndWait",
        "approvalOptions": {
          "values": {
            "approveLabel": "Publish Reply"
          }
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "b01c19b7-57e9-47f8-af45-3d5b535d0689",
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "position": [
        3152,
        1152
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-approved",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.data.approved }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "02c68c2c-c5ab-476c-8cf6-01ba394fd654",
      "name": "Publish Reply",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        3392,
        1104
      ],
      "parameters": {
        "url": "=https://api.trustpilot.com/v1/reviews/{{ $('Process Each Review').item.json.reviewId }}/reply",
        "method": "POST",
        "options": {
          "timeout": 10000
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "message",
              "value": "={{ $('Draft Reply (AI Agent)').item.json.output.draftReply }}"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "63a99087-30fa-42ba-836d-cc7de88fe06d",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        1104,
        1488
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "44e2b2db-a0e7-470a-b9ed-e426ae1b7ff6",
      "name": "Notify Ops",
      "type": "n8n-nodes-base.slack",
      "position": [
        1344,
        1488
      ],
      "parameters": {
        "text": "=\ud83d\udea8 AI Review & Reputation Manager failed: {{ $json.execution.error.message }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "REPLACE_WITH_CHANNEL_ID",
          "cachedResultName": "ops-alerts"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.3
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "af5af0e2-a868-4e0a-8ba4-8401442e7ca3",
  "nodeGroups": [],
  "connections": {
    "Approved?": {
      "main": [
        [
          {
            "node": "Publish Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Process Each Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every Hour": {
      "main": [
        [
          {
            "node": "Fetch Google Reviews",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Trustpilot Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Notify Ops",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Publish Reply": {
      "main": [
        [
          {
            "node": "Process Each Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Sources": {
      "main": [
        [
          {
            "node": "Dedupe New Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any New Reviews?": {
      "main": [
        [
          {
            "node": "No New Reviews",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Process Each Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Negative Review?": {
      "main": [
        [
          {
            "node": "Escalate to Owner",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Request Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request Approval": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Escalate to Owner": {
      "main": [
        [
          {
            "node": "Process Each Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Draft Reply (AI Agent)",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Dedupe New Reviews": {
      "main": [
        [
          {
            "node": "Any New Reviews?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Each Review": {
      "main": [
        [
          {
            "node": "Limit To Done",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Draft Reply (AI Agent)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Google Reviews": {
      "main": [
        [
          {
            "node": "Combine Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Reply (AI Agent)": {
      "main": [
        [
          {
            "node": "Negative Review?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Trustpilot Reviews": {
      "main": [
        [
          {
            "node": "Combine Sources",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Draft Reply (AI Agent)",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  }
}