Automation · Revenue operations
How to build lead-to-account matching
A lead from a subsidiary of your largest customer arrives looking like a brand new company. The thinking behind matching that catches it, the prompts that build it, and what it takes to keep it running.
Built with Tray Headless
- System Salesforce leads
- Step Strip generic domains
- Step Domain match
- Step Fuzzy name score
- Step Hierarchy walk
- System Salesforce accounts
Three passes in order, and the middle confidence band goes to a person instead of being guessed.
The short answer
What is lead-to-account matching?
Done well, lead-to-account matching is four things: a domain match that handles the cases domains cannot, a fuzzy name match with a confidence score instead of a yes or no, a walk up the account hierarchy so subsidiaries land on the parent relationship, and a review queue for the matches that are close but not certain. The common failure is treating it as a boolean. A match at 0.71 confidence is not a match and not a miss, and auto-accepting it puts a lead on the wrong account permanently.
What matters here
- Score the match instead of deciding it. Auto-accept high, auto-reject low, and send the middle band to a human.
- Strip generic domains before matching anything. Otherwise every consumer email address matches whichever account has gmail.com in a field.
- Walk the account hierarchy. A subsidiary that matches nothing at the top level usually matches its parent, and that relationship is the reason to match at all.
- Store the confidence and the method on the lead. When somebody disputes a match, the answer has to be inspectable.
- Re-run matching on unmatched leads nightly. Accounts get created after leads arrive, and a one-shot matcher never catches up.
Who this is for
You run revenue operations or marketing operations. Leads arrive that belong to accounts you already have, and nothing connects them, so a rep works a lead that a colleague is already three calls into. What you want is matching you can inspect and correct.
How it works in practice
The path from a lead arriving to it being attached to the right account.
- 1
Generic domains are stripped first
Gmail, Outlook, Yahoo and the rest never match on domain, because consumer addresses belong to people instead of companies.
- 2
Domain matches against the account website
Exact first, then the registrable domain, so a lead from mail.acme.co.uk still finds acme.co.uk.
- 3
Fuzzy name matching produces a score, not a verdict
Normalised for punctuation and legal suffixes, so Acme Ltd and Acme Limited are the same company.
- 4
The hierarchy is walked upward
A subsidiary with no direct match is tested against its parent, which is usually where the relationship already exists.
- 5
The middle band goes to a human
High confidence auto-accepts, low auto-rejects, and the uncertain ones queue for review rather than guessing.
- 6
Unmatched leads are re-tried nightly
Because the account often gets created a week after the lead did.
What matching is made of
Matching is not a lookup. It is four parts, and the fourth is the one that keeps it honest.
A domain match that handles the exceptions
Generic domains stripped, subdomains reduced to the registrable domain, and multiple domains per account supported, because acquisitions leave a trail.
A fuzzy name match with a score
Normalised for punctuation, legal suffixes and casing, producing a confidence figure instead of a boolean.
A hierarchy walk
Up two levels through the parent chain, so a subsidiary lands on the relationship that already exists rather than becoming a new logo.
A review queue for the middle band
Anything between the auto-accept and auto-reject thresholds waits for a person. That band is where the damage happens if you guess.
The Tray Headless prompts
Paste these into Claude Code or Codex with the Tray Headless plugin installed. Each stage runs on its own. The systems named in them are the worked example rather than a requirement, and every prompt says so.
Once per project, run
/tray-workflows:set-workspace
to pick the workspace these build in. Point it at a sandbox first.
- 1
Set up and get the real field names
Guessing at the CRM schema is how matchers end up broken in production.
Headless skills
build-workflowUse build-workflow. The systems in play are Salesforce, or whatever we run in those seats. I need the real query operations and field names for Lead, Account and Opportunity, including any custom domain field the account object uses. Do not assume Account.Website is the only place a domain lives.
- 2
Match on domain, handling the cases domains do not
Most matches resolve here, and most bad matches start here too.
Headless skills
build-workflowUse build-workflow to build the domain matcher: 1. Extract the domain from Lead.Email 2. Reject generic domains outright: gmail, outlook, hotmail, yahoo, icloud, proton, aol, and anything on a list I can extend 3. Reduce to the registrable domain, so mail.acme.co.uk becomes acme.co.uk 4. Match against Account.Website and any additional domain field 5. Support several domains per account, because acquisitions leave a trail and the old domain still sends leads A domain match is confidence 1.0. Record the method as "domain" so anybody auditing a match can see how it was made. - 3
Fuzzy match the company name, and score it
The part that must produce a number, not a verdict.
Now the name matcher, for leads with no domain match. Normalise both sides before comparing: lowercase, strip punctuation, and remove legal suffixes so Acme Ltd, Acme Limited, Acme Inc and Acme LLC all reduce to acme. Score the similarity between Lead.Company and Account.Name. Then: above 0.90 accept automatically, method "name-high" 0.75 to 0.90 send to the review queue, method "name-review" below 0.75 no match Store both the score and the method on the lead. When somebody disputes a match, the answer has to be inspectable rather than a shrug.
The thresholds matter less than having three bands rather than two. Pick numbers, then move them once you have seen a week of the review queue.
- 4
Walk the hierarchy before giving up
Where the largest accounts get matched, and where a boolean matcher fails.
For any lead still unmatched, walk the account hierarchy. Take the fuzzy candidates that scored below the accept threshold, follow Account.ParentId up two levels, and test the parent name too. A subsidiary that matches nothing on its own name usually matches its parent, and that parent relationship is the whole reason to match. If a parent matches, attach the lead to the subsidiary account and record that the match was made through the hierarchy, not directly. The rep needs to know which entity they are actually talking to.
- 5
Queue the uncertain ones and re-try the misses
Matching is a running process, not a one-shot decision.
Headless skills
tray-patternsBuild the review queue: lead, candidate account, score, method, and one click to accept or reject. Feed every decision back so the thresholds can be tuned against real outcomes, not guessed at. Then schedule a nightly re-run over all unmatched leads from the last 90 days. Accounts get created after leads arrive, so a matcher that only runs on insert never catches up. Report weekly: match rate by method, the size of the review queue, how long items sit in it, and how often a human overturns an auto-accept. That last number is the one that tells you a threshold is wrong.
- 6
Validate, then hand it over
So the thresholds belong to the team that owns the data.
Run the per-step schema checks and the whole-workflow audit before this touches production. Then open the same workflow in Tray Build so revenue operations can adjust a threshold or extend the generic domain list in the visual canvas. Those numbers will change, and they should not need a coding assistant to change them.
What it connects to
Matching reads the account universe and writes a decision back onto the lead.
Salesforce
Read leads, accounts and the parent hierarchy. Write the matched account, the confidence and the method that produced it.
Reads and writes
Clearbit
Resolve a company from an email domain when the CRM has nothing to match against yet.
Reads
Marketo
Read inbound leads at source, so matching happens before routing instead of after somebody has already been assigned.
Reads
Slack
Post the review queue to the person who owns account data, rather than hoping they open a report.
Writes
Snowflake
Land every match decision with its score, so threshold tuning is analysis instead of opinion.
Writes
Outreach
Suppress outbound to a lead that just matched an account with an open opportunity, before two people call the same company.
Writes
Same build, other stacks
The design does not change if you run something else in one of these seats. The same prompts build it against Microsoft Dynamics 365, Google BigQuery, Microsoft Teams, HubSpot, Databricks or Google Chat.
Named systems are the ones most teams run, not the only ones that work. Each is an authentication in your Tray workspace, referenced by name, so the workflow never holds a credential. Where we have a connector page, the name links to it.
Running it in production
Matching writes to the record every other revenue process reads. Getting it wrong is expensive and quiet.
It runs on the platform, not on your laptop
Insert-time matching and the nightly re-run both execute on the platform, so a matcher that fails at 3am retries rather than skipping a night.
Every match is attributable
Score, method and timestamp on each decision, with the platform audit trail behind it. A disputed match has an answer.
Managed credentials, not secrets in a config file
Read access to the whole account universe is a privileged thing to hold. It lives as an authentication in your workspace, scoped and revocable without touching the matcher.
Ops owns the thresholds
The accept and reject bands and the generic domain list open in Tray Build, so the team that watches the review queue can tune them.
The review queue is monitored, not just created
Alert when it grows past a size or an age. An unwatched review queue is the same as auto-accepting everything, just slower.
Questions people ask
Why not just match on email domain?
Because consumer addresses have no company domain, subsidiaries use their own, and acquisitions leave old domains in circulation. Domain matching resolves most leads and misses exactly the largest accounts.
What confidence threshold should I use?
Start at 0.90 to auto-accept and 0.75 to auto-reject, then move both after a week of watching the review queue. The specific numbers matter less than having a middle band at all.
Why walk the account hierarchy?
Because a subsidiary of a major customer looks like a new logo on its own name. Walking up two levels through the parent chain finds the relationship that already exists.
How often should matching re-run?
Nightly, over unmatched leads from the last 90 days. Accounts are frequently created after the lead arrives, so a matcher that only fires on insert leaves those leads orphaned forever.
Who changes the matching thresholds once it is live?
Yes. The workflow is built from a coding assistant and opens in Tray Build, so thresholds and the generic domain list are edits by the team that owns the data.
Further reading
Background on the same subject, for the case rather than the build.
Related guides
Revenue operations
How to build lead routing that assigns in seconds
Match the lead to an account first, evaluate rules in a fixed order, catch what they miss, and time it from arrival. The Headless prompts that build it.
Revenue operations
How to build lead import and validation
Validate before you write, quarantine the bad rows instead of dropping them, dedupe against what is already there, and keep the source. The prompts that build it.
Revenue operations
How to build a billing to CRM sync
Put invoice and payment state where the account team works, without letting the CRM become a second billing system. The Headless prompts that build it.
Last reviewed September 2026.