Skip to main content

n8n Workflow: Tracking Page Views, Visit History, and Multi-Day Engagement with RB2B

Updated over a week ago

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:

  1. RB2B_Page_Visits — every individual RB2B event (raw log)

  2. 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

  1. Logs the visit, every time RB2B sends a visitor event

  2. Adds a row to RB2B_Page_Visits

  3. Normalizes the profile date, ensuring clean fields for name, company, LinkedIn URL, etc.

  4. Updates their master profile row

    1. If they've visited before:

      1. increments page_views

      2. updates last_seen

      3. updates last_page

      4. appends the latest page + timestamp to all_pages

      5. recalculates unique_days and unique_days_count

    2. If they are new:

      1. creates a brand new row with initial data

      2. 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_key

  • Value: ={{ $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

Downloads

Did this answer your question?