Build a Customer Feedback Sentiment Pipeline in n8n
Reading through 300 survey responses by hand to find out whether customers are happy or frustrated is one of those tasks that sounds manageable at first, then slowly eats an afternoon every week. Sentiment analysis automates exactly that: every new review, survey answer, or support ticket gets a score and a label, trends surface on their own, and you get a Slack ping when something goes sideways — without touching the data yourself.
This guide builds that pipeline step by step in n8n, using the AI/LLM node to do the heavy lifting.
What the pipeline does
- Ingests new feedback on a schedule or via a webhook push.
- Scores each item with an AI node — positive / neutral / negative, plus 1–3 extracted themes.
- Writes the enriched row to a Google Sheet (your running log).
- Alerts to Slack when the rolling negative ratio in the last 50 items crosses 30 %.
By the end you will have a workflow you can leave running indefinitely.
The nodes, in order
1. Schedule Trigger (or Webhook)
Schedule Trigger is the easiest entry point. Set it to run every 30 minutes:
Trigger interval: Every 30 minutes
If your review source can push events in real time (Typeform, a custom app, Stripe's review.created webhook), use a Webhook node instead — it gives you a POST endpoint like https://<your-id>.agentroost.app/webhook/feedback that you can paste straight into any third-party webhook settings panel.
2. HTTP Request or Google Sheets (ingest)
Pull the unprocessed rows from wherever you store feedback.
Option A — Google Sheets:
Add a Google Sheets node in "Read Rows" mode. Point it at a sheet called raw_feedback. Filter to rows where column processed is empty so you only touch new entries.
Option B — HTTP Request: If feedback comes from a REST endpoint (your own product API, a Typeform results API, etc.):
{
"method": "GET",
"url": "https://api.yourapp.com/feedback?since={{ $now.minus(30, 'minutes').toISO() }}",
"authentication": "headerAuth"
}
Use a Split In Batches node after ingest if you expect more than ~20 items at a time, to keep LLM calls predictable.
3. Basic LLM Chain (sentiment + themes)
This is the core. Add a Basic LLM Chain node (under the AI section in n8n). Set the system prompt:
You are a product feedback analyst. Given a customer review, return ONLY valid JSON with these fields:
- sentiment: "positive" | "neutral" | "negative"
- score: a float from -1.0 (most negative) to 1.0 (most positive)
- themes: an array of 1-3 short lowercase strings (e.g. ["slow load time", "great support"])
- one_line_summary: one sentence, max 15 words
Review:
{{ $json.feedback_text }}
Set the user message to {{ $json.feedback_text }} and choose any available model. Because the node returns plain text, add a Code node (or Set node with an expression) to parse it:
// Code node — JavaScript
const parsed = JSON.parse($input.first().json.text);
return [{ json: { ...parsed, original: $input.first().json } }];
Why not the full AI Agent node here? Agents are great for multi-step reasoning. For a single structured extraction like this, Basic LLM Chain is faster, cheaper on tokens, and the output is predictable JSON.
4. Set — flatten for the sheet
A Set node maps the parsed fields to flat column names:
| Field | Value expression |
|---|---|
feedback_id |
{{ $json.original.id }} |
feedback_text |
{{ $json.original.feedback_text }} |
sentiment |
{{ $json.sentiment }} |
score |
{{ $json.score }} |
themes |
{{ $json.themes.join(', ') }} |
summary |
{{ $json.one_line_summary }} |
processed_at |
{{ $now.toISO() }} |
5. Google Sheets — write enriched row
Add a second Google Sheets node in "Append Row" mode. Point it at a sheet called sentiment_log. Map the columns from the Set node above. Also add another Google Sheets node in "Update Row" mode back on raw_feedback to mark the row processed = true so the Schedule Trigger skips it next time.
6. IF — spike detection
Add an IF node. The condition reads the last 50 rows of sentiment_log and checks the negative ratio. One clean way: add a Google Sheets node in "Read Rows" mode (limit 50, sorted by processed_at descending), then a Code node that counts negatives:
const rows = $input.all().map(i => i.json);
const negCount = rows.filter(r => r.sentiment === 'negative').length;
const ratio = negCount / rows.length;
return [{ json: { ratio, negCount, total: rows.length, spike: ratio > 0.3 } }];
The IF node branches on {{ $json.spike }} === true.
7. Slack (alert branch)
On the true branch, add a Slack node in "Send Message" mode:
Channel: #product-alerts
Message: Feedback spike: {{ $json.negCount }} of the last {{ $json.total }} reviews are negative ({{ ($json.ratio * 100).toFixed(0) }}%). Check the sentiment log.
On the false branch, add a No Operation node or nothing — the workflow ends quietly.
Tips and common pitfalls
Prompt the model to return ONLY JSON. Without "return ONLY valid JSON" in the system prompt, models often wrap output in markdown fences or add a preamble. The Code node's JSON.parse will throw. Add a try/catch and route parse errors to a separate error sheet so nothing silently drops.
Batch carefully. If you have hundreds of reviews queued up on first run, the 30-minute schedule will call the LLM hundreds of times back-to-back. Use Split In Batches (batch size 10) with a Wait node (2 seconds) between batches to stay within rate limits.
Theme vocabulary drift. Free-form themes like "load time" and "slow loading" are duplicates to a human but treated as different strings. Once you have a few weeks of data, review the themes column and feed a short canonical list back into the prompt: Classify themes using ONLY these labels: [billing, performance, onboarding, support, feature-request].
Test with a Webhook trigger first. Before enabling the Schedule Trigger for production, use a manual Webhook test run with three sample rows — one positive, one neutral, one negative — so you can verify the JSON parsing and the Slack alert without waiting 30 minutes.
Run it on AgentRoost
Getting a workflow like this running on your own server means provisioning a VPS, installing n8n, wiring up SSL, and then creating and paying for OpenAI API credentials separately. The AI node cost is invisible until the invoice arrives.
On AgentRoost you sign up, pick the n8n framework, name your instance, and your private n8n editor opens at https://<your-id>.agentroost.app in about two minutes. You own the instance — your login, your workflows, your data. The AI/LLM node is already connected to included credits — no API key to create, no separate billing account. The public HTTPS URL on your instance means the Webhook trigger works from the first minute, no ngrok or Cloudflare tunnel.
The base plan starts at $19.99/mo all-in. There is a 14-day money-back guarantee if the workflow does not do what you need.
See what's included in each plan — or go straight to your own n8n instance to get the editor open.
Frequently asked questions
Do I need an OpenAI API key to use the AI/LLM node in n8n?
Not on AgentRoost. AI/LLM credits are included in every subscription, so the AI node is pre-wired and works out of the box. If you self-host n8n on your own server you would need to supply your own API key and pay separately for each model call.
Can I connect my own OpenAI or Anthropic key instead of using the included credits?
Yes. You can always add your own credentials in n8n's credential vault if you prefer a specific model or want to use your existing API spend. The included credits are there so you don't have to — not a lock-in.
How do I get review data into the pipeline — do I need a webhook?
Both approaches work. A Schedule Trigger polling a Google Sheet or a database table is the simplest start. A Webhook node gives you a real-time push endpoint (your instance already has a public HTTPS URL on AgentRoost, so no tunneling needed) — useful for Typeform, Stripe, or your own app.
What happens to my data? Does AgentRoost see my customer reviews?
Your n8n instance is single-tenant — it runs in an isolated workspace. The review data flows through your own instance and is not shared with other customers or inspected by AgentRoost. LLM calls go to the model provider through a proxy that counts tokens against your included credit balance.
Can I export my workflows if I ever want to move?
Yes. n8n's built-in export lets you download any workflow as a JSON file at any time. You own your workflows and your data — there is no proprietary format locking you in.