Luka Mrkić
Head of BD
Insights, strategies, and real-world playbooks on AI-powered marketing.
JUN 1, 2026
If you are evaluating who should build this for your team, this guide gives you both the technical blueprint and the standards to evaluate the work.
A review monitoring workflow watches a fixed set of public review pages on a fixed schedule and tells you when a new review lands, what it is about, how negative or positive it reads, and who on your team should see it. The pages are usually your own G2 and Capterra listings plus the top three or four competitors in your category. The schedule is usually every six to twelve hours. The output is rarely the raw review text. It is the classified, deduplicated alert that drops into the right channel.
The old way of doing this was a Friday review of the G2 inbox and a quarterly competitor sweep. That cadence misses the review where a prospect explains exactly why they bounced from your free trial. It misses the moment three reviews on a competitor list the same missing feature. It misses the champion quote that should have been in the sales deck the day it was published.
The automated version of this runs while you sleep, catches every new review within hours, and turns each one into a Slack post, a HubSpot note, or a product board entry. The team sees themes emerge in real time.

Five nodes inside a single n8n workflow. A schedule trigger that fires the run. A fetch group that pulls reviews from G2 and Capterra for each tracked listing. A dedupe step that compares each review id to the store and drops what has already been processed. A Claude classification call that returns structured JSON. A router that sends the alert to Slack, HubSpot, and a product board based on theme and severity. A small log node closes the loop so you can audit what fired and what did not.
Start with your own G2 and Capterra pages plus three competitor pages on each platform. Six to eight listings total is the right starting size. Past that, alert volume climbs faster than usable signal and the team learns to ignore the channel by week two.
Treat the listing list as data. Store it in a Google Sheet or an Airtable base with columns for vendor, source, url, category, owner, and active. The owner column is the single most important column in the entire workflow. Every alert downstream resolves to a name through this table.
listings.csv
vendor,source,url,category,owner,active
acme,g2,https://www.g2.com/products/acme/reviews,own,pmm@example.com,true
acme,capterra,https://www.capterra.com/p/12345/acme/,own,pmm@example.com,true
beta,g2,https://www.g2.com/products/beta/reviews,competitor,ci@example.com,true
gamma,g2,https://www.g2.com/products/gamma/reviews,competitor,ci@example.com,true
n8n gives you two viable fetch paths. The first is an HTTP Request node hitting a managed scraping API (Firecrawl, Bright Data, ScrapingBee) that returns the listing page as clean markdown or JSON. The second is a Code node that drives a headless browser through n8n’s execution environment. The HTTP path is the boring default. Public review pages render heavily on the client, sit behind anti-bot layers, and change markup often. Operating your own scraper is a project; you want this build to stay one workflow.
Respect the source terms of service and the rate limits of whatever scraping layer you use. Six to twelve hour intervals are enough to catch everything without putting pressure on either platform. Hourly polling is rarely worth the spend or the risk of throttling.
// n8n HTTP Request node config
{
"method": "POST",
"url": "https://api.firecrawl.dev/v1/scrape",
"authentication": "genericCredentialType",
"sendHeaders": true,
"headerParameters": {
"Authorization": "=Bearer {{$credentials.firecrawl.apiKey}}"
},
"sendBody": true,
"bodyParameters": {
"url": "={{$json.url}}",
"formats": ["markdown"],
"onlyMainContent": true
}
}
Parse the returned markdown with a small Code node. You are looking for review blocks. Each block has a review id (or a stable hash of author plus date plus first sentence), a rating, a date, a title, a body, and sometimes pros and cons fields. Normalize all of that into a flat array of review objects before the next step.
A review monitoring workflow that fires every six hours will pull the same review hundreds of times across its lifetime. Without dedupe, the alert channel becomes wallpaper and the model bill grows linearly with how often the workflow runs. Dedupe is the single highest leverage step in the build.
Two storage choices. A Postgres table with one row per review id and a timestamp is the durable answer. A Google Sheet with a review_id column is fine for a first build and lets non engineers inspect the store by eye. Either way, the contract is the same. For each incoming review, check whether the id already exists. If yes, drop it. If no, insert it and pass it to the classify step.
// n8n Code node: dedupe against Postgres
const pg = require('pg');
const client = new pg.Client({ connectionString: $credentials.pg.connectionString });
await client.connect();
const fresh = [];
for (const review of items.map(i => i.json)) {
const r = await client.query(
'select 1 from reviews where review_id = $1 limit 1',
[review.review_id]
);
if (r.rowCount === 0) {
await client.query(
'insert into reviews (review_id, vendor, source, seen_at) values ($1, $2, $3, now())',
[review.review_id, review.vendor, review.source]
);
fresh.push({ json: review });
}
}
await client.end();
return fresh;
Only what survives this step reaches Claude. On most runs, on most listings, nothing is new and the model is not called. That is what makes the monthly bill stay small even on a six hour cadence.

This is where the workflow earns its keep. A weak prompt returns a paragraph of prose and you are back to the manual triage problem with extra steps. A strong prompt returns structured JSON that the router can read without parsing English.
// n8n HTTP Request node: Claude classify
{
"method": "POST",
"url": "https://api.anthropic.com/v1/messages",
"sendHeaders": true,
"headerParameters": {
"x-api-key": "={{$credentials.anthropic.apiKey}}",
"anthropic-version": "2023-06-01"
},
"sendBody": true,
"bodyParameters": {
"model": "claude-sonnet-4-5",
"max_tokens": 600,
"system": "You classify a single customer review. Themes: features, pricing, onboarding, support, competitor, champion, other. Return strict JSON with keys theme, sentiment (-1.0 to 1.0), severity (1-5), summary (one sentence, max 25 words), quote (exact sentence to lift), owner_hint (one of: pmm, revops, support, product, founders).",
"messages": [{
"role": "user",
"content": "=Vendor: {{$json.vendor}}\nSource: {{$json.source}}\nRating: {{$json.rating}}\nTitle: {{$json.title}}\n\nReview:\n{{$json.body}}"
}]
}
}
Parse the message content with a Code node and store the structured fields on the item. The owner_hint field is what bridges into the routing step; you map hint to channel and to CRM owner in a single small table.
If you want this set up cleanly inside your stack with logging, retries, and a feedback loop into a CRM, that is the kind of work we ship at Espressio.
The router does not need to be smart. The listing table already labels each page as own or competitor. The classifier returned a theme, a severity, and an owner hint. The router is a Switch node feeding three or four destinations: Slack for human channels, HubSpot for CRM notes when the review touches a known account, and an Airtable or Linear node for the product board when the theme is features or onboarding.
# Slack message template
*{{theme.upper()}}* · sev {{severity}} · {{vendor}} on {{source}}
{{summary}}
> {{quote}}
{{url}}
Two design choices in this router matter. Severity gating drops cosmetic alerts (sev 1 typo praise, throwaway one-liners) before they ever post. And channel by theme means a pricing complaint reaches RevOps the same day, a feature gap reaches Product on the same day, and a champion quote reaches PMM with the quote already pulled.
A small append node into a log table closes the loop. Each row records the run timestamp, listings polled, new reviews found, alerts fired, and alerts dropped by the severity gate. The log table is what you read when somebody asks why the channel went quiet for two days. Most of the time the answer is that nothing new was published; sometimes the answer is that the scraper started failing on one source and you need to fix it.

Six themes are enough for the first version. Feature gaps and pricing friction are the two that pay back the build fastest because they feed directly into the next sales call and the next pricing review. Champion quotes are the third because the best lines on G2 and Capterra are worth more than any quote a copywriter can draft. The other three are useful once routing on the first three is reliable.

An alert volume metric on its own does not tell you whether the workflow is useful. The number that matters is action rate: how many alerts in the last week led to a CRM note, a sales deck edit, a product ticket, or a customer follow up. Track action rate weekly. If it trends to zero, retune the severity gate and the theme list before you blame the model.
Both platforms publish public review pages and both have terms of service that govern automated access. Read each platform’s terms before you turn the workflow on, use a managed scraping layer that respects robots and rate limits, and watch your fetch cadence. For higher volume needs, both platforms offer official partner programs and data feeds. If your account or a customer relationship depends on the platform, use the official feed.
n8n gives you Code nodes, native Postgres and HTTP nodes, self hosted runs, and per execution pricing that stays predictable at scale. Zapier is faster to start but the code and storage primitives get expensive once dedupe and per-theme routing are in the loop. Make is in between. The workflow translates one for one to either of the other two if you already standardized on them.
Sonnet handles single review classification well at a reasonable price. Haiku is fast and cheap but gives up consistency on the JSON contract under load. Opus is overkill. Sonnet is the default until you have a reason to switch.
Add the source as another row in the listing table and reuse the same fetch, dedupe, classify, and route nodes. The only thing that changes is the parsing step, because each source publishes review blocks in a slightly different markdown shape. Write a small per-source parser and keep the rest of the workflow shared.
Two cost lines: the scraping layer and the model. For six to eight listings polled every six hours, with dedupe short circuiting most runs, list-price spend lands in the low tens of dollars a month. n8n cloud or self hosted runs add a fixed line; the workflow’s variable cost stays small because Claude is only called for genuinely new reviews.
If you want automation like this set up cleanly inside your customer intelligence stack, let’s talk.