Skip to content

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

  1. System Salesforce leads
  2. Step Strip generic domains
  3. Step Domain match
  4. Step Fuzzy name score
  5. Step Hierarchy walk
  6. System Salesforce accounts
Also Review queue

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

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

    Match on domain, handling the cases domains do not

    Most matches resolve here, and most bad matches start here too.

    Headless skills build-workflow

    Use 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. 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. 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. 5

    Queue the uncertain ones and re-try the misses

    Matching is a running process, not a one-shot decision.

    Headless skills tray-patterns

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

Last reviewed September 2026.