To match technographic data to CRM accounts, you decide for every account which company website it stands for, if any. This guide sets out the process for a revenue operations or data team on any warehouse: normalising the domain that serves as the join key, ordering the fallback keys, handling accounts with several domains, and measuring the match rate and its errors on your own account list. The queries join Technology Stack, the Fokals dataset of the technologies each company runs, to an account table by domain.
How do you match technographic data to CRM accounts?
Match on the website domain. Reduce the website on each account and the domain in the technographic dataset to one normalised form, join the two, and record how each account matched. Send the rest through fallback keys in order: a listing identifier where the account holds one, then the exact company name with its country, checked by hand.
The domain comes first because of what it names. Technographic data is observed on a website, so the website is the level at which it joins exactly. A CRM account also seldom carries an ISIN or an LEI. Where both sides carry one, as with a portfolio's holdings, it is the stronger key and comes first, as the post on the identifiers that make company data joinable sets out, alongside what each key names.
The other side of the join is Technology Stack, in the marketing stack dataset. It has one row for each company website and technology. Each row carries the website's domain, the stable Fokals company ID, the company name, the first-seen and last-seen dates of the technology and, when the company or its parent is listed, the listing identifiers. The data dictionary defines each field.
Deciding which account and which website are one company is entity resolution, and domain matching is its first step. The guide to joining licensed company data to CRM tables in Snowflake builds the join as views in one warehouse. This page is the process behind it, in standard SQL.
Domain normalisation: one form for the join key
Two spellings of one website do not join, so both sides pass through the same rules before any match. Each rule rests on how a web address and a domain name are defined.
| Rule | Why it holds |
|---|---|
| Keep the host alone | RFC 3986 lists scheme, authority, path, query and fragment as the components of a URI, and the host as one part of the authority |
| Lower-case the host | The same RFC states that the host is case-insensitive and that normalisers should use lowercase |
| Remove a trailing dot | RFC 9499 notes that a fully qualified name can be written with a terminating dot, as in www.example.net. |
| Write an internationalised name in ASCII | RFC 5890 defines the A-label, which begins xn--, and the Unicode U-label, each convertible to the other |
| Compare whole labels | RFC 9499 notes that ooo.example.com is not a subdomain of oo.example.com |
Remove a leading www | A convention: on both sides or on neither |
Keep two values for each address: the normalised host and its registrable domain, which is the public suffix plus one preceding label. RFC 9499 states that nothing in a domain name indicates whether it is a public suffix, which can only be determined by outside means, and names the Public Suffix List maintained by Mozilla as one resource. A rule that keeps the last two labels is therefore a guess.
Each engine below parses the address for you.
| Engine | Function | What its documentation says it returns |
|---|---|---|
| BigQuery | NET.HOST and NET.REG_DOMAIN, among its Net functions | The host of a URL, and the registered or registrable domain: the public suffix plus one preceding label |
| Snowflake | PARSE_URL | An object that holds the components of a URL, the host among them |
| Databricks | parse_url with the part 'HOST' | The requested part of the URL |
| Trino | url_extract_host, among its URL functions | The host of a URL |
Three details deserve a test in any engine. Google's BigQuery reference shows NET.REG_DOMAIN returning Example.Co.UK for www.Example.Co.UK: the public suffix has two labels, and the result keeps the case of the input, so lower-case it yourself. It also states that the function does not treat a private domain in the public suffix data as a public suffix: in its example foo.us.com reduces to us.com. A host under a shared parent reduces to that parent, which is why the registrable domain comes after the host in the match and never before it. Trino documents the syntax its URL extraction functions support as [protocol:][//host[:port]][path][?query][#fragment], in which the host follows //.
Keep a file of test inputs with the output you expect from each: an upper-case address with a port, a trailing dot, a bare address with no scheme, such as the illustrative acme-robotics.example/about, an internationalised name and an empty cell. Run it against both sides after every change to the normaliser.
The matching process for technographic data enrichment: a waterfall over CRM accounts
The matching process is a waterfall. Each CRM account passes through an ordered list of keys and stops at the first key that finds exactly one company; technographic enrichment is then written for that company. The order runs from reviewed decisions to the normalised host, the registrable domain, a listing identifier and the company name.
| Step | Key | Accept when | Record the basis as |
|---|---|---|---|
| 1 | A reviewed decision | A person has already ruled for or against a company | Override |
| 2 | Normalised host | The host equals a website domain in Technology Stack | Host |
| 3 | Registrable domain | The host is a subdomain, and its registrable domain equals a website domain | Registrable domain |
| 4 | ISIN, LEI, or ticker with MIC | The account carries the identifier, and it equals the one on the rows | Identifier |
| 5 | Name with country | The normalised names are equal and a person accepts the candidate | Name |
Steps 2 and 3 follow in standard SQL, over a table of your own, account_domains, with one row per account and host and with host and reg_domain already normalised.
with websites as (
select distinct domain, company_id
from company_technologies
),
candidates as (
select a.account_id, a.host, w.company_id,
w.domain as matched_domain, 'host' as match_basis, 2 as step
from account_domains a
join websites w on w.domain = a.host
union all
select a.account_id, a.host, w.company_id,
w.domain, 'reg_domain', 3
from account_domains a
join websites w on w.domain = a.reg_domain
where a.reg_domain <> a.host
),
ranked as (
select c.*,
min(step) over (partition by account_id, host) as best_step,
count(*) over (partition by account_id, host, step) as hits
from candidates c
)
select account_id, host, company_id, matched_domain, match_basis,
case when hits = 1 then 'matched' else 'review' end as status
from ranked
where step = best_step;Here websites is the map from website to company, read from Technology Stack (company_technologies) with its domain passed through the same normaliser on load. Each host of an account stops at its first step that finds a company. A host that finds two companies at that step gets the status review and is never guessed. Save the result as account_matches.
Step 4 serves a listed company whose account carries no usable website. In Fokals data a brand or subsidiary carries the identifiers of its listed parent, so one ISIN can return the parent and each of its brands. Read an identifier match as a match to the group, and keep the member whose website or name fits the account.
Step 5 produces candidates, never matches. Normalise the name on both sides: lower-case it, remove punctuation and remove the legal form, taking the forms from a published list. GLEIF publishes the ISO 20275 code list of entity legal forms, which holds legal forms in their native language, such as GmbH or Société Anonyme. For a listed company, the listed securities table gives the issuer's country to compare with the billing country. Each candidate arrives with the website of the company it found: the reviewer compares that website with the account and, on acceptance, adds the domain to the account, so the next run matches it at step 2.
With the matches saved, enrichment is one join on the company ID.
select distinct m.account_id, m.match_basis, t.domain,
t.technology_name, t.technology_category,
t.first_seen_at, t.last_seen_at
from account_matches m
join company_technologies t on t.company_id = m.company_id
where m.status = 'matched'
and t.missing_since is null;The join is on company_id, so an account matched through one website receives the technologies of every website of that company, and domain shows where each was detected. The filter on missing_since leaves out a technology that is absent from recent observations. A detection shows presence on the website, so write last_seen_at into the CRM beside each technology.
Accounts with several domains
An account can hold more than one domain: the website field, a second domain on the record, the domains of the email addresses filed under it. Keep them all, one row per account and host, with the field each came from. HubSpot's documentation states that it deduplicates companies using domain names, that an import by default compares the primary and secondary values of the Company domain name property, and that companies created through its API are not deduplicated by that property. Plan for both shapes: an account with several domains, and several accounts with one.
| What the account's domains find | Read it as | What to do |
|---|---|---|
| One company | One business with several websites | Match, and keep every domain on the account |
| Several companies that share an ISIN | A listed parent and its brands | Match each, and group by ISIN when the question is the group |
| Several unrelated companies | Two businesses merged into one record, or a domain that belongs to someone else | Review, then split the account or remove the domain |
Several accounts that match one company are sound: a head office and its regional accounts, or a duplicate. Enrich each, and count the company once in any report. The data has the same shape: one company can have several websites, and each brand in the Fokals index has a website of its own, so an account that holds any one of them finds its company.
A shared domain is the opposite case: a hosting platform, a marketplace or a free-mail service typed into the website field. It shows as one domain matched by many accounts, and this query lists the suspects.
select matched_domain,
count(distinct account_id) as accounts
from account_matches
group by matched_domain
having count(distinct account_id) >= 5
order by accounts desc;Read the top of the list by hand. Put each shared domain on a block list that empties the host before the match, and leave the domain of a large group in place. The threshold of five is a starting point.
How to measure a match rate and its errors on your own account list
A match rate says how many accounts found a company. It is silent on how many found the right one. Four measures together describe a match, and the last two need a person.
| Measure | Formula | What it tells you |
|---|---|---|
| Coverage | Matched accounts over all accounts | How much of the CRM will be enriched |
| Match rate | Matched accounts over accounts with a usable domain | How well the key works where it exists |
| Precision | True links over all links made | How many matches are right |
| Recall | True links over true links plus missed links | How many of the findable companies were found |
The terms are those of record linkage. Guidance on quality assessment in data linkage, published by the UK's Office for National Statistics and Government Analysis Function, names the two errors false links and missed links and defines precision and recall as in the table. It adds that clerical review, a person ruling on each sampled pair, is more useful for estimating precision than for estimating recall.
- Precision. Have a person rule on a random sample of matches from each step, and report the false share by step.
- Recall. Search the dataset by hand for a random sample of unmatched accounts. Each is either a missed link, where the company is in the dataset under a key the account lacks, or a company outside the dataset's index. Only the first is an error of the match.
- Bias. Compare matched and unmatched accounts by country, size and source of the record. The same guidance notes that comparing linked and unlinked records can give evidence of how missed links are distributed.
- Uncertainty. A share measured on a sample has a margin. The NIST/SEMATECH e-Handbook of Statistical Methods sets out the Wilson method for a confidence interval on a proportion, applied in the example below.
The Fokals index is listed companies worldwide, the brands they own and verified private companies, and the coverage page shows this week's figures.
A worked example: measuring the match on an illustrative account list
Acme Robotics is an invented company and every figure here is illustrative. Its data team matches the CRM to Technology Stack and measures the result.
Assumptions:
- The CRM holds 12,000 accounts, 10,200 of them with a usable domain after normalisation.
- Step 2 matches 6,630 accounts on the host and step 3 a further 510 on the registrable domain. No account carries an identifier.
- Step 5 proposes 300 name candidates among the accounts still unmatched, and a reviewer accepts 204, which are taken as true.
- A reviewer rules on 200 random matches from step 2 and finds 1 false, and on 200 from step 3 and finds 14 false.
- A reviewer searches the dataset for 200 random unmatched accounts and finds 10 under a key the account lacks; missed links are counted among the unmatched accounts only, so the recall is an upper estimate.
- Intervals use the Wilson method at 95%, with z equal to 1.96.
The match rate on the domain is 6,630 plus 510 over 10,200: 7,140 over 10,200, or 70.0%. Coverage is 7,140 plus 204 over 12,000: 7,344 over 12,000, or 61.2%. That leaves 4,656 accounts unmatched.
| Step | Matched | False in the sample | False share | Estimated false links |
|---|---|---|---|---|
| Host | 6,630 | 1 of 200 | 0.5% | 33 |
| Registrable domain | 510 | 14 of 200 | 7.0% | 36 |
| Name, reviewed | 204 | Reviewed in full | Taken as 0% | 0 |
| All steps | 7,344 | 69 |
True links are 7,344 less 69, which is 7,275, so precision is 7,275 over 7,344, or 99.1%. Missed links are 10 of 200, or 5.0%, and 5.0% of 4,656 is 233. Recall is 7,275 over 7,275 plus 233: 7,275 over 7,508, or 96.9%.
The Wilson interval for 14 false in 200 runs from 4.2% to 11.4%, so step 3 holds between 21 and 58 false links among its 510 matches. It made about 7% of the domain matches and over half of the estimated false links, 36 of 69. The decision follows: review all 510 once and store each ruling as an override.
Best practices for matching technographic data to CRM accounts by domain and company name
Match on the normalised domain and treat the company name as a candidate for review, never as a key. Keep the host and the registrable domain apart, record the basis of every match, hold anything that finds two companies, and measure precision and recall on a reviewed sample before the enrichment reaches the CRM.
- One normaliser for both sides, kept with its test inputs and run on every load.
- The host first, then the registrable domain, reviewed until its false share is known.
- A block list of shared domains, fed by the count of accounts per matched domain.
- A name match only when the names are equal, the country agrees and a person accepts it.
- An overrides table that every run reads first, so each ruling is made once.
- The basis, the matched domain and the last-seen date written into the CRM with each technology.
- Coverage, match rate, precision by step and recall, reported by segment after every load.
With the accounts matched, the use case on finding accounts by the technology they run builds target lists on the same rows.
Frequently asked questions
What is a matching waterfall in CRM enrichment?
A matching waterfall is an ordered list of keys that each account passes through, stopping at the first key that finds exactly one company. Stronger keys run first and weaker keys see only what is left: reviewed decisions, the normalised host, the registrable domain, a listing identifier, then the company name with its country. Recording which step matched lets you measure and review each step on its own.
Should I match on the full host or the registrable domain?
Both, in that order. The full host names the exact website, so it is the strictest key. The registrable domain, which is the public suffix plus one preceding label, also catches country and shop subdomains, and it reduces a site hosted under a shared parent to that parent. Match the host first and the registrable domain after it, and review the registrable-domain matches until you know their false share.
How do I match a CRM account that has no website?
Use an identifier if the account carries one: an ISIN, an LEI, or a ticker with its market identifier code. Otherwise compare the normalised company name, require the country to agree and send every candidate to a person. When a candidate is accepted, add the company's domain to the account, so the account matches on the domain from then on.
What is the difference between match rate, coverage, precision and recall?
Coverage is matched accounts over all accounts. Match rate is matched accounts over accounts that have a usable domain. Precision is the share of matches that are right, estimated by reviewing a sample of matches. Recall is the share of findable companies that were found, estimated by searching the dataset by hand for a sample of unmatched accounts. A match rate becomes a result once precision stands beside it.
How often should technographic data be matched to the CRM again?
After every load of the dataset, and whenever a website field changes in the CRM. Technology Stack is refreshed daily to weekly, companies add websites, and accounts are created and merged, so a match made once goes stale. Read the overrides first on every run, keep each run's matches in a dated table, and report the four measures each time, so a drop is seen when it happens.
The queries and code on this page are examples to adapt. Test them in your own environment before you rely on them.
What this page says about the products it names was checked against their public documentation on 7 October 2026. Product and company names are trademarks of their owners. Fokals is not affiliated with them or endorsed by them.