Fill your CRM with who is actually visiting
The job: your CRM has the accounts your reps created. Signal has the accounts that are reading your site. The overlap is smaller than anyone expects, and the gap is your pipeline.
Who this is for: anyone running HubSpot, Salesforce or a spreadsheet that wishes it were one.
Do the comparison before you build anything
signal_domains = {a["company_domain"] for a in get_all("/accounts")}
crm_domains = {c["domain"] for c in crm.list_accounts()}
print("in both:", len(signal_domains & crm_domains))
print("visiting, not in CRM:", len(signal_domains - crm_domains))
print("in CRM, not visiting:", len(crm_domains - signal_domains))
Run that first. The three numbers tell you which problem you actually have, and they are usually not the one you assumed. A large "visiting, not in CRM" is demand you are not working. A large "in CRM, not visiting" is a pipeline review waiting to happen.
Creating only what is worth creating
Do not sync everything. Most identified visitors are not prospects, and a CRM full of them is worse than one missing them:
def worth_creating(a):
return (
a["company_domain"] != "personal"
and a["classification"] == "lead"
and a["intent_score"] >= 50
and a["visitor_count"] >= 2 # more than one human looked
)
visitor_count versus visit_count matters here: one person visiting fifteen
times is a researcher; three people visiting twice each is an account.
Writing back, idempotently
for a in filter(worth_creating, accounts):
crm.upsert_account(
domain=a["company_domain"],
name=a["company_name"] or a["company_domain"],
properties={
"signal_intent": a["intent_score"],
"signal_people": a["visitor_count"],
"signal_first_seen": a["first_seen_at"],
"signal_last_seen": a["last_seen_at"],
},
)
Key on company_domain. It is the natural key on both sides and the only field
that will not drift — company names change spelling constantly.
Letting an LLM do the triage
When "visiting, not in CRM" is a few hundred rows, a model is better than a threshold at separating real prospects from noise:
Here are companies visiting our site that are not in our CRM:
{accounts_json}
We sell {one sentence about your product} to {your ICP}.
Split them into: create now, watch, and ignore. For each in "create now", one
sentence citing the specific numbers that justify it. Put anything you cannot
tell apart into "watch" rather than guessing — an over-full CRM costs us more
than a missed account.
What will go wrong
Duplicates. Your CRM probably has acme.com, www.acme.com and
Acme Corp as three records already. Normalise before you compare, or you will
create a fourth.
Subsidiaries. acme.co.uk and acme.com are one customer to a human and
two domains here.
total is capped. If you are near your plan's resolution cap, get_all
returns what you may resolve, not everything that exists. The response says so;
do not treat the count as the population.
Next: score and route inbound leads · export and keep in sync
Markdown source: /developers/workflows/fill-your-crm-with-who-is-actually-visiting.md