The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Automated Review Responder (Claude + GPT)",
"nodes": [
{
"id": "1",
"name": "Cron Trigger",
"type": "n8n-nodes-base.cron",
"typeVersion": 1,
"position": [
200,
200
],
"parameters": {
"triggerTimes": {
"item": [
{
"hour": "*",
"minute": "*/5"
}
]
}
}
},
{
"id": "2",
"name": "List Reviews - Google Business Profile",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
480,
200
],
"parameters": {
"method": "GET",
"url": "https://mybusiness.googleapis.com/v4/accounts/YOUR_ACCOUNT_ID/locations/YOUR_LOCATION_ID/reviews",
"responseFormat": "json"
},
"credentials": {
"httpHeaderAuth": "<your credential>"
}
},
{
"id": "3",
"name": "Parse Reviews",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
720,
200
],
"parameters": {
"functionCode": "// Flatten Google response into one review per item\nconst data = items[0].json || {};\nconst reviews = data.reviews || [];\nreturn reviews.map(r => {\n const reviewId = r.name ? r.name.split('/').pop() : null;\n const ratingRaw = r.starRating || r.rating || null;\n let rating = null;\n if (typeof ratingRaw === 'string') {\n const m = ratingRaw.match(/\\d+/);\n rating = m ? parseInt(m[0], 10) : null;\n } else if (typeof ratingRaw === 'number') {\n rating = ratingRaw;\n }\n return {\n json: {\n reviewId,\n comment: r.comment || '',\n rating,\n reviewer: r.reviewer?.displayName || ''\n }\n };\n});"
}
},
{
"id": "4",
"name": "Split Reviews",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 1,
"position": [
960,
200
],
"parameters": {
"batchSize": 1
}
},
{
"id": "5",
"name": "Check Processed",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1200,
200
],
"parameters": {
"functionCode": "const item = items[0].json;\nconst staticData = getWorkflowStaticData('global');\nstaticData.processedReviews = staticData.processedReviews || {};\nconst already = staticData.processedReviews[item.reviewId] ? 'yes' : 'no';\nreturn [{ json: { ...item, alreadyProcessed: already } }];"
}
},
{
"id": "6",
"name": "If Not Processed",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1440,
200
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{$json[\"alreadyProcessed\"]}}",
"operation": "equal",
"value2": "no"
}
]
}
}
},
{
"id": "7",
"name": "If Positive (>=4)",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1680,
200
],
"parameters": {
"conditions": {
"number": [
{
"value1": "={{$json[\"rating\"]}}",
"operation": "greaterThanOrEqual",
"value2": 4
}
]
}
}
},
{
"id": "8",
"name": "OpenAI GPT Reply",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1920,
120
],
"parameters": {
"method": "POST",
"url": "https://api.openai.com/v1/chat/completions",
"authentication": "headerAuth",
"sendBody": true,
"jsonParameters": true,
"body": {
"model": "gpt-4o-mini",
"messages": [
{
"role": "system",
"content": "You write friendly replies to positive reviews. Thank warmly, keep it casual yet professional, under 80 words."
},
{
"role": "user",
"content": "Review: {{$json[\"comment\"]}} | Rating: {{$json[\"rating\"]}}"
}
],
"max_tokens": 200
}
},
"credentials": {
"httpHeaderAuth": "<your credential>"
}
},
{
"id": "9",
"name": "Claude Reply",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1920,
280
],
"parameters": {
"method": "POST",
"url": "https://api.anthropic.com/v1/messages",
"authentication": "headerAuth",
"sendBody": true,
"jsonParameters": true,
"body": {
"model": "claude-3-5-sonnet-20240620",
"max_tokens": 350,
"system": "You are a professional customer success manager writing replies to critical reviews. Apologize, acknowledge, invite them to continue privately. Under 120 words.",
"messages": [
{
"role": "user",
"content": "Review: {{$json[\"comment\"]}} | Rating: {{$json[\"rating\"]}}"
}
]
}
},
"credentials": {
"httpHeaderAuth": "<your credential>"
}
},
{
"id": "10",
"name": "Merge Positive",
"type": "n8n-nodes-base.merge",
"typeVersion": 1,
"position": [
2160,
120
],
"parameters": {
"mode": "mergeByIndex"
}
},
{
"id": "11",
"name": "Merge Negative",
"type": "n8n-nodes-base.merge",
"typeVersion": 1,
"position": [
2160,
300
],
"parameters": {
"mode": "mergeByIndex"
}
},
{
"id": "12",
"name": "Normalize Reply",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
2440,
200
],
"parameters": {
"functionCode": "const d = items[0].json;\nlet reply = '';\nif (d.choices?.[0]?.message?.content) reply = d.choices[0].message.content;\nif (d.content?.[0]?.text) reply = d.content[0].text;\nreturn [{ json: { reviewId: d.reviewId, replyText: reply } }];"
}
},
{
"id": "13",
"name": "Post Reply - GBP",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2720,
200
],
"parameters": {
"method": "POST",
"url": "https://mybusiness.googleapis.com/v4/accounts/YOUR_ACCOUNT_ID/locations/YOUR_LOCATION_ID/reviews/{{$json[\"reviewId\"]}}/reply",
"sendBody": true,
"jsonParameters": true,
"body": {
"comment": "={{$json[\"replyText\"]}}"
}
},
"credentials": {
"httpHeaderAuth": "<your credential>"
}
},
{
"id": "14",
"name": "Mark Processed",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
3000,
200
],
"parameters": {
"functionCode": "const id = items[0].json.reviewId;\nif (id) {\n const s = getWorkflowStaticData('global');\n s.processedReviews = s.processedReviews || {};\n s.processedReviews[id] = new Date().toISOString();\n}\nreturn items;"
}
}
],
"connections": {
"Cron Trigger": {
"main": [
[
{
"node": "List Reviews - Google Business Profile",
"type": "main",
"index": 0
}
]
]
},
"List Reviews - Google Business Profile": {
"main": [
[
{
"node": "Parse Reviews",
"type": "main",
"index": 0
}
]
]
},
"Parse Reviews": {
"main": [
[
{
"node": "Split Reviews",
"type": "main",
"index": 0
}
]
]
},
"Split Reviews": {
"main": [
[
{
"node": "Check Processed",
"type": "main",
"index": 0
}
]
]
},
"Check Processed": {
"main": [
[
{
"node": "If Not Processed",
"type": "main",
"index": 0
}
]
]
},
"If Not Processed": {
"main": [
[
{
"node": "If Positive (>=4)",
"type": "main",
"index": 0
}
]
]
},
"If Positive (>=4)": {
"main": [
[
{
"node": "OpenAI GPT Reply",
"type": "main",
"index": 0
}
],
[
{
"node": "Claude Reply",
"type": "main",
"index": 0
}
]
]
},
"OpenAI GPT Reply": {
"main": [
[
{
"node": "Merge Positive",
"type": "main",
"index": 1
}
]
]
},
"Claude Reply": {
"main": [
[
{
"node": "Merge Negative",
"type": "main",
"index": 1
}
]
]
},
"Merge Positive": {
"main": [
[
{
"node": "Normalize Reply",
"type": "main",
"index": 0
}
]
]
},
"Merge Negative": {
"main": [
[
{
"node": "Normalize Reply",
"type": "main",
"index": 0
}
]
]
},
"Normalize Reply": {
"main": [
[
{
"node": "Post Reply - GBP",
"type": "main",
"index": 0
}
]
]
},
"Post Reply - GBP": {
"main": [
[
{
"node": "Mark Processed",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
httpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automated Review Responder (Claude + GPT). Uses httpRequest. Scheduled trigger; 14 nodes.
Source: https://gist.github.com/sambinofrank15/43e63f1b67ad1f6f74c694acbb2ef358 — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
As n8n instances scale, teams often lose track of sub-workflows—who uses them, where they are referenced, and whether they can be safely updated. This leads to inefficiencies like unnecessary copies o
This workflow is an improvement of this workflow by Greg Brzezinka.
N8N-Workflow-Github-Manager. Uses github, httpRequest, n8n. Scheduled trigger; 38 nodes.
This workflow uses KlickTipp community nodes, available for self-hosted n8n instances only.
This workflow acts as an automated engagement bot. It sends a Direct Message (DM) with a link or resource to any follower who replies to your post with a specific target keyword.