This workflow will be the foundation of a number of other RB2B x n8n workflows.
This guide walks you through building an advanced n8n workflow for RB2B users that tracks:
Every page view from each identified visitor
Full visit history, including timestamps
Total page views per person
Unique days they visited your site
A chronological timeline of all RB2B page visits
Returning-visitor handling (update instead of overwrite)
By the end, you’ll have two Google Sheets:
RB2B_Page_Visits — every individual RB2B event (raw log)
RB2B_Person_Visits — one row per visitor, continuously updated
This workflow is perfect for:
Lead scoring
Engagement analysis
“Return visitor” alerts
Account-based outreach
Funnel and path analysis
Hot lead identification based on multi-day interest
What This Workflow Does
Logs the visit, every time RB2B sends a visitor event
Adds a row to
RB2B_Page_VisitsNormalizes the profile date, ensuring clean fields for name, company, LinkedIn URL, etc.
Updates their master profile row
If they've visited before:
increments
page_viewsupdates
last_seenupdates
last_pageappends the latest page + timestamp to
all_pagesrecalculates
unique_daysandunique_days_count
If they are new:
creates a brand new row with initial data
initializes all tracking fields
Google Sheets Setup
You’ll use two sheets. Structure them exactly like this:
RB2B_Page_Visits (raw events)
One row per RB2B webhook event with these columns:
identity_key
seen_at
captured_url
referrer
tags
first_name
last_name
title
company_name
business_email
website
industry
employee_count
estimate_revenue
city
state
zipcode
RB2B_Person_Visits (per-visitor summary)
One row per person with these columns:
identity_key
first_seen
last_seen
page_views
last_page
last_referrer
first_name
last_name
title
company_name
business_email
website
industry
employee_count
estimate_revenue
city
state
zipcode
tags
all_pages
unique_days
unique_days_count
Step-By-Step n8n Workflow
1. Webhook (RB2B → n8n)
Create a Webhook node that receives RB2B visitor payloads. This is your workflow trigger.
2. Code Node: Normalize the Data + Compute identity_key
Name this node: Normalize Visitor
Paste:
// The RB2B payload is inside "body"
const item = $json.body || {};
// Prefer LinkedIn URL, fallback to Business Email
const identity_key = item["LinkedIn URL"] || item["Business Email"] || "";
// Derive account_key from Website domain (e.g. "https://retention.com" -> "retention.com")
let account_key = "";
if (item["Company Name"]) {
account_key = item["Company Name"];
}
// Clean, reliable helper for name fallback
function normalizeName(value, fallback) {
if (!value || typeof value !== "string") return fallback;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : fallback;
}
// Ensure ONLY missing names get fallback values
const first_name = normalizeName(item["First Name"], "Anonymous");
const last_name = normalizeName(item["Last Name"], "Visitor");
return [
{
identity_key,
account_key,
first_name,
last_name,
title: item["Title"] || "",
company_name: item["Company Name"] || "",
business_email: item["Business Email"] || "",
website: item["Website"] || "",
industry: item["Industry"] || "",
employee_count: item["Employee Count"] || "",
estimate_revenue: item["Estimate Revenue"] || "",
city: item["City"] || "",
state: item["State"] || "",
zipcode: item["Zipcode"] || "",
seen_at: item["Seen At"] || "",
referrer: item["Referrer"] || "",
captured_url: item["Captured URL"] || "",
tags: item["Tags"] || ""
}
];
3. Google Sheets: Append Page Visit
Connect this node from Normalize Visitor.
Operation: Append Row
Sheet:
RB2B_Page_Visits
Map all normalized fields. This creates a full history of everything RB2B sends you.
4. Google Sheets: Get Existing Person Row
Connect another branch from Normalize Visitor.
Operation: Get Row(s)
Column to match:
identity_keyValue:
={{ $json.identity_key }}
Name this node exactly: Get row(s) in sheet
You’ll reference that name in the next step.
5. IF Node: Returning vs New Visitor
Use your working condition:
Resource: Expression
Value 1:
={{ Object.keys($('Get row(s) in sheet').item.json).length > 0 }}Operation: Is true
TRUE → returning visitor
FALSE → new visitor
Branch A: NEW VISITOR (FALSE)
Add a Code node: Build New Visitor Row
const incoming = $('Normalize Visitor').item.json;
// Extract date (YYYY-MM-DD)
const seenAt = incoming.seen_at || "";
const day = seenAt ? seenAt.slice(0, 10) : null;
// First page in array
const allPages = incoming.captured_url && seenAt
? [{ url: incoming.captured_url, seen_at: seenAt }]
: [];
const uniqueDays = day ? [day] : [];
return [
{
identity_key: incoming.identity_key,
first_seen: incoming.seen_at,
last_seen: incoming.seen_at,
page_views: 1,
last_page: incoming.captured_url,
last_referrer: incoming.referrer,
first_name: incoming.first_name,
last_name: incoming.last_name,
title: incoming.title,
company_name: incoming.company_name,
business_email: incoming.business_email,
website: incoming.website,
industry: incoming.industry,
employee_count: incoming.employee_count,
estimate_revenue: incoming.estimate_revenue,
city: incoming.city,
state: incoming.state,
zipcode: incoming.zipcode,
tags: incoming.tags,
all_pages: JSON.stringify(allPages),
unique_days: JSON.stringify(uniqueDays),
unique_days_count: uniqueDays.length
}
];Then:
Add a Google Sheets Append Row node to write this into
RB2B_Person_Visits.

Branch B: RETURNING VISITOR (TRUE)
Add a Code node: Build Returning Visitor Row
// existing sheet row
const sheetRow = $('Get row(s) in sheet').item.json;
// incoming event
const incoming = $('Normalize Visitor').item.json;
// page views
const currentViews = Number(sheetRow.page_views || 0);
const newViews = currentViews + 1;
// rebuild all_pages array
let pages = [];
try {
if (sheetRow.all_pages) {
pages = JSON.parse(sheetRow.all_pages);
}
} catch (e) {
pages = [];
}
// ALWAYS append new visit (even duplicates)
if (incoming.captured_url && incoming.seen_at) {
pages.push({
url: incoming.captured_url,
seen_at: incoming.seen_at
});
}
// build unique_days
const daySet = new Set();
for (const p of pages) {
if (p.seen_at) {
daySet.add(p.seen_at.slice(0, 10));
}
}
const uniqueDays = Array.from(daySet);
const uniqueDaysCount = uniqueDays.length;
return [
{
rowNumber: sheetRow.rowNumber,
identity_key: sheetRow.identity_key,
first_seen: sheetRow.first_seen,
last_seen: incoming.seen_at,
page_views: newViews,
last_page: incoming.captured_url,
last_referrer: incoming.referrer,
first_name: incoming.first_name,
last_name: incoming.last_name,
title: incoming.title,
company_name: incoming.company_name,
business_email: incoming.business_email,
website: incoming.website,
industry: incoming.industry,
employee_count: incoming.employee_count,
estimate_revenue: incoming.estimate_revenue,
city: incoming.city,
state: incoming.state,
zipcode: incoming.zipcode,
tags: incoming.tags,
all_pages: JSON.stringify(pages),
unique_days: JSON.stringify(uniqueDays),
unique_days_count: uniqueDaysCount
}
];
Then:
Add a Google Sheets Update Row node
Row number:
={{ $json.rowNumber }}Map all fields into
RB2B_Person_Visits








