Your team lives in Notion. The weekly pipeline doc is there, the ICP notes are there, the account research is there. What is not there is a live list of the accounts actually showing up on your site right now. So every Monday the team reviews the same stale accounts while the real buyers are clicking around your pricing page in a different tab.
I am Elene, and this is the Notion setup I most commonly get asked for from marketing and RevOps leads who want a single “who is on our site” page the whole team opens in the morning. This guide gets you a live accounts watchlist in about 25 minutes, with views that segment by intent, industry, and target-account status.
What you will build
A Notion database that updates in near real time, one row per identified account, with contact info, intent score, pages viewed, and a “new this week” flag. Plus three pre-built views: hot accounts, target accounts, and return visitors.
Prerequisites
| Requirement | Notes |
|---|---|
| Leadpipe account | Start free with 500 identifications |
| Notion workspace | Any plan with API access (all paid plans have it) |
| Zapier or Make account | Optional for no-code; Make is cheaper at volume |
| Notion API key | Free, created from your workspace settings |
| Time | 25 minutes |
If you are thinking about a CRM-style system-of-record rather than a read-only watchlist, start with the Attio recipe or Close CRM recipe instead. This Notion setup is for teams who want a shared reference doc, not a full CRM.
The data model
Notion’s database-as-a-page structure works better for this than most people expect.
Notion database: "Website visitors (live)"
├─ Account (title) e.g. Acme.io
├─ Domain acme.io
├─ Industry B2B SaaS
├─ Employees 180
├─ Last visitor Maria Lopez
├─ Last visitor title Head of Growth
├─ Last email
├─ Last page
├─ Intent score 1-100
├─ Intent topics multi-select
├─ First seen date
├─ Last seen date
├─ Return visitor checkbox
├─ Target account checkbox (set manually or by logic)
└─ Source select: First Match / Update
You want one row per account, not per visitor. If Maria and Raj from Acme.io both show up, you update the row for acme.io rather than creating two. That is what makes it a watchlist instead of a log.
Step 1: Create the Notion database
Open Notion, create a new full-page database called “Website visitors (live)”. Add the columns from the data model above. Be careful with types, they control how Zapier maps later.
| Column | Notion type |
|---|---|
| Account | Title |
| Domain | URL |
| Industry | Select |
| Employees | Number |
| Last visitor | Text |
| Last visitor title | Text |
| Last email | |
| Last page | Text |
| Intent score | Number |
| Intent topics | Multi-select |
| First seen | Date |
| Last seen | Date |
| Return visitor | Checkbox |
| Target account | Checkbox |
| Source | Select |
Pre-populate Industry options (B2B SaaS, E-commerce, Fintech, Healthcare, Manufacturing, Services, Other) and Intent topics (your top 20 categories from Leadpipe) so Zapier does not error on unknown options later.
Step 2: Get your Notion API credentials
- Go to notion.so/my-integrations. Click New integration.
- Name it “Leadpipe watchlist”. Associated workspace: your team’s workspace. Capabilities: Read content, Update content, Insert content.
- Copy the Internal Integration Secret. This is your API key.
- Open your watchlist database. Click the three-dot menu, Connections, pick your new integration.
- Also copy the database ID. It is the 32-character string in the URL after your workspace name and before the query parameter.
Step 3: Leadpipe webhook
Familiar starting point if you have read any of our other integration recipes (see Zapier automation recipes or the Slack alerts guide).
- In Leadpipe: Settings, Integrations, Webhooks, Add Webhook.
- Destination URL: generated in Step 4.
- Trigger: First Match plus Every Update. You want both here because Notion is a watchlist, and return visits should update Last seen and Intent score.
- Fields: include everything. The full schema lives in our webhook payload reference.
Example payload:
{
"email": "maria.lopez@acme.io",
"first_name": "Maria",
"last_name": "Lopez",
"phone": "+1-415-555-0139",
"company_name": "Acme.io",
"company_domain": "acme.io",
"company_industry": "B2B SaaS",
"company_employee_count": 180,
"job_title": "Head of Growth",
"seniority": "Head",
"department": "Marketing",
"linkedin_url": "linkedin.com/in/marialopezgrowth",
"city": "San Francisco",
"state": "California",
"country": "US",
"page_url": "/pricing",
"pages_viewed": ["/", "/features", "/pricing"],
"visit_duration": 241,
"intent_score": 78,
"matched_topics": ["sales engagement", "CRM"],
"return_visit": false
}
For EU and UK visitors, Leadpipe defaults to company-level. Your Notion row for a German visitor will usually show Account and Domain filled in with empty Last visitor and Last email fields. That is correct under GDPR (see our GDPR compliance post).
Step 4: Zapier path
- New Zap. Trigger: Webhooks by Zapier, Catch Hook. Copy the URL, paste into Leadpipe.
- Test trigger. Zapier shows the full payload.
- Filter.
visit_durationgreater than 20.emaildoes not containgmail.com, yahoo.com, outlook.com. - Formatter step. Lowercase
company_domain, stripwww.. This is your upsert key, so it has to be clean. - Action: Notion, Find Database Item. Database: your watchlist. Filter: Domain equals the cleaned domain.
- Path split (Zapier’s Paths feature):
- Path A: no existing row. Action: Notion, Create Database Item. Fill all fields. Set First seen and Last seen to now. Set Source to “First Match”.
- Path B: row exists. Action: Notion, Update Database Item. Update Last seen, Last visitor, Last visitor title, Last email, Last page, Intent score (take max of old and new), Intent topics (append new). Set Return visitor to true if the new payload’s return_visit is true or if this is the second or later write.
This logic is slightly more intricate than the Attio upsert because Notion does not have a native upsert endpoint. The Find + Path + Create/Update pattern is how people do it.
Step 5: Direct API path (cleaner at volume)
If you send more than a few hundred identifications per day, skip Zapier and call Notion’s API from a middleware.
// POST /leadpipe-webhook → Notion database
// Env: NOTION_API_KEY, NOTION_DATABASE_ID
import { Client } from '@notionhq/client';
const notion = new Client({ auth: process.env.NOTION_API_KEY });
const dbId = process.env.NOTION_DATABASE_ID;
export async function handler(req) {
const v = req.body;
const domain = v.company_domain?.toLowerCase().replace(/^www\./, '');
if (!domain) return { statusCode: 200, body: 'no domain' };
// Look for existing row
const existing = await notion.databases.query({
database_id: dbId,
filter: { property: 'Domain', url: { equals: `https://${domain}` } },
page_size: 1,
});
const now = new Date().toISOString();
const props = {
Account: { title: [{ text: { content: v.company_name || domain } }] },
Domain: { url: `https://${domain}` },
Industry: v.company_industry
? { select: { name: v.company_industry } }
: undefined,
Employees: { number: v.company_employee_count || null },
'Last visitor': {
rich_text: [
{ text: { content: `${v.first_name || ''} ${v.last_name || ''}`.trim() } },
],
},
'Last visitor title': {
rich_text: [{ text: { content: v.job_title || '' } }],
},
'Last email': { email: v.email || null },
'Last page': { rich_text: [{ text: { content: v.page_url || '' } }] },
'Intent score': { number: v.intent_score || null },
'Intent topics': {
multi_select: (v.matched_topics || []).map((t) => ({ name: t })),
},
'Last seen': { date: { start: now } },
'Return visitor': { checkbox: !!v.return_visit },
Source: { select: { name: v.return_visit ? 'Update' : 'First Match' } },
};
if (existing.results.length) {
await notion.pages.update({ page_id: existing.results[0].id, properties: props });
} else {
await notion.pages.create({
parent: { database_id: dbId },
properties: { ...props, 'First seen': { date: { start: now } } },
});
}
return { statusCode: 200, body: 'ok' };
}
Deploy on Vercel or Cloudflare Workers. A few hundred writes per day comes in under the free tier everywhere.
Step 6: Build the views your team will actually use
Raw databases are useless without views. Build these three inside the same database.
View 1: Hot accounts this week
- Filter: Last seen is in the past 7 days AND Intent score is greater than 60
- Sort: Intent score descending, then Last seen descending
- Layout: Table
View 2: Target accounts on site
- Filter: Target account is checked AND Last seen is in the past 30 days
- Sort: Last seen descending
- Layout: Table, with a grouping by Industry
View 3: Return visitors
- Filter: Return visitor is checked AND Last seen is in the past 14 days
- Sort: Last seen descending
- Layout: Board view grouped by Intent topics (visual, helps spot clusters)
Pin the three views to the top. Put the database on your team’s dashboard page. Nothing else, just the three views.
What this looks like in practice
Monday, 8:52 am. Your demand gen lead opens the team’s weekly operating page and clicks “Hot accounts this week” first.
| Account | Industry | Employees | Last visitor | Title | Last page | Intent |
|---|---|---|---|---|---|---|
| Acme.io | B2B SaaS | 180 | Maria Lopez | Head of Growth | /pricing | 78 |
| Northwind Labs | Logistics SaaS | 320 | James Park | Director Demand Gen | /pricing | 74 |
| Orbit.dev | DevTools | 95 | Raj Mehta | VP Marketing | /enterprise | 71 |
| Fieldwise | Fintech | 240 | Tomoko Ito | Marketing Ops | /case-studies | 62 |
| Sendhaus | Marketing | 60 | Paula Fernandez | Founder | /pricing | 60 |
She picks the top three, posts them in the sales Slack channel with one line each. The AEs know what to work before their first call. That is a 10-minute morning ritual that replaces an hour of CRM scrolling.
Troubleshooting and edge cases
Duplicate accounts. Almost always a domain normalization bug. Acme.io, www.acme.io, ACME.IO all create separate rows unless you lowercase and strip. Do it in the formatter before the Find step.
Unknown Industry or Intent topic values. Notion’s Select and Multi-select fields need the option to exist before you can write it. Pre-populate your top 20 industries and top 40 topics. Configure your middleware to skip unknown values rather than fail the whole write.
Slow queries. If your watchlist crosses 5,000 rows, the Find step gets slow and Zapier times out. This is when you move to the direct API path, which handles the same queries in under 400ms.
GDPR EU rows. Expect a subset of rows with Account and Domain filled and all the person-level fields empty. That is by design. For those rows, still count them toward Last seen and Intent score so the watchlist stays useful at account level.
Notion rate limits. The Notion API caps at 3 requests per second per integration. If your traffic spikes, queue writes in a middleware function instead of firing them synchronously. For most teams this never becomes an issue.
Team accidentally edits rows. Set the database to read-only for most users, or add a Notion automation that reverts changes to certain columns. Watchlists are fragile when someone manually overrides the Intent score.
Extending the recipe
- Pair with Slack alerts for real-time and use the Notion view for the morning review.
- Feed the same data into Airtable for routing logic if you want to turn the watchlist into assigned workloads.
- Connect Notion to a CRM like Attio or Close for the full record-of-truth loop (Notion for browsing, CRM for working).
- Use the Leadpipe MCP server to let Claude or ChatGPT query the watchlist directly. Your weekly review becomes “Claude, give me the five most interesting accounts from the past seven days and why.”
Why a watchlist in Notion beats a spreadsheet or dashboard
A spreadsheet goes stale the moment you stop editing it. A dashboard has too many controls and nobody opens it after the first week. Notion sits in the middle: it is where the team already thinks, and a live database there is exactly one click from the pages they open daily.
The best intel is the intel people actually read.
If you want the short version: $147/mo gets you person-level identification on 500 visitors with full contact data. See full pricing →