Skip to content

Automation  ·  Revenue operations

How to build lead enrichment that does not stall the queue

Enrichment is the step that turns a form fill into something routable, and the step most likely to hold a lead in a queue while an API times out. What follows is the model behind enrichment that fails open, the prompts that build it, and what changes in production.

Built with Tray Headless

  1. System Marketo
  2. Step Cache check
  3. System Clearbit
  4. System ZoomInfo
  5. System Salesforce
Also Timeout, route anyway

The cache is checked before any provider is called, and the timeout releases the lead instead of holding it.

The short answer

What is lead enrichment?

Lead enrichment is four parts: a cache keyed on domain so the same company is never bought twice, a provider chain that falls back rather than failing, a hard timeout that lets routing proceed on partial data, and a record of which provider supplied each field. The part that goes wrong most often is the timeout. Enrichment that blocks routing turns a five second API problem into a lead that sat unassigned overnight.

What matters here

  • Fail open, never closed. A lead with no firmographics still routes; a lead stuck waiting for firmographics does not.
  • Cache on the registrable domain. Enriching the same company forty times in a month is the most common way credits disappear.
  • Chain providers rather than trusting one. Coverage differs by geography and company size, and the second provider is cheaper than the missed lead.
  • Record which provider gave you each field. When a value is wrong, you need to know where it came from before you can fix it.
  • Never let enrichment overwrite a value a human typed. Sales corrections are data, not noise.

Who this is for

You run marketing or revenue operations. Routing and scoring both depend on firmographics you do not collect on the form, so enrichment sits in the critical path, and when it is slow everything behind it is slow.

How it works in practice

Everything that sits between a form fill and a routable lead.

  1. 1

    The domain is extracted and normalised

    Generic consumer domains are marked as unenrichable immediately, not sent to a provider that will charge for nothing.

  2. 2

    The cache is checked first

    If the company was enriched in the last 90 days, use it. Firmographics do not change weekly and credits are finite.

  3. 3

    The provider chain runs in order

    Primary, then fallback on a miss. Coverage differs by region and size, and one provider is never enough.

  4. 4

    A hard timeout releases the lead

    Routing proceeds on whatever arrived. Enrichment continues in the background and updates the record when it lands.

  5. 5

    Fields are written with their source

    Provider and timestamp per field, so a wrong value can be traced instead of argued about.

  6. 6

    Human-entered values are never overwritten

    A rep who corrected the employee count knows something the provider does not.

What enrichment is made of

Enrichment is not an API call. It is four parts, and the third one keeps the pipeline moving.

A cache keyed on domain

Ninety day freshness on the registrable domain. The same company arrives repeatedly and should be bought once.

A provider chain

Primary then fallback, with per-provider coverage tracked so the order can be changed on evidence rather than on a contract renewal.

A hard timeout that fails open

A few seconds, then routing proceeds. Enrichment catches up asynchronously and updates the record.

Provenance per field

Which provider supplied each field, and on what date. A rep who says the job title is wrong deserves a reply better than the CRM says so.

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, then check what enrichment is even connected

    Provider coverage is the constraint, so establish it first.

    Headless skills build-workflow

    Use build-workflow. Tell me which enrichment providers are
    authenticated and what the CRM connection is.
    
    Tell me what is missing. Do not stub a provider I have not authenticated,
    and do not assume a single provider covers everything.
  2. 2

    Cache before you buy anything

    The single change that most reduces enrichment spend.

    Headless skills build-workflow

    Use build-workflow. Before any provider call:
    
      1. Extract and reduce the email domain to the registrable domain
      2. Mark generic consumer domains as unenrichable and skip the call
         entirely, do not spend a credit finding out
      3. Look the domain up in a cache table keyed on registrable domain
      4. If the record is under 90 days old, use it and stop
    
    Write every successful enrichment back to that cache with a timestamp.
    The same company arrives repeatedly from different people, and buying it
    each time is how a credit budget disappears by October.
  3. 3

    Chain the providers, and measure them

    One provider never covers everything, and coverage is testable.

    Build the provider chain. Call the primary first. On a miss or a partial
    result, call the fallback for the fields still empty. Never call both
    when the first one succeeded.
    
    Merge the results field by field rather than taking whichever responded
    last. Record which provider supplied each field.
    
    Then report coverage by provider, split by region and by company size
    band. Provider order should be a decision you make from that table, not
    from whoever sold to you most recently.

    Coverage differs sharply outside North America. If the report is not split by region it will hide the gap that matters most.

  4. 4

    Time it out, and fail open

    The rule that keeps enrichment out of the critical path.

    Headless skills tray-gotchas

    Use tray-gotchas, then set a hard timeout of a few seconds on the whole
    enrichment step.
    
    When the timeout fires, routing proceeds on whatever data exists. Do not
    hold the lead. Continue enrichment in the background and update the
    record when it lands, then re-evaluate routing only if a field that
    affects a rule actually changed.
    
    A lead with no employee count still routes, to the fallback if it must.
    A lead that sat in a queue overnight waiting for an employee count is a
    lost lead and a support ticket.
  5. 5

    Write carefully, and never overwrite a human

    Because sales corrections carry information the provider lacks.

    When writing enriched fields back:
    
      Never overwrite a value that was entered by a person. Track which
      fields are human-entered and treat them as authoritative.
    
      Never overwrite a non-empty field with an empty one. A provider
      returning null is not a correction.
    
      Stamp each enriched field with its provider and timestamp.
    
    Then report the enrichment rate, the cache hit rate, the timeout rate and
    spend per enriched lead. If the cache hit rate is below half, the cache
    key is wrong.
  6. 6

    Check it end to end, then hand it over

    So provider order and freshness are tunable by the team that pays for it.

    Run the per-step schema checks and the whole-workflow audit before this
    touches production.
    
    Then open the same workflow in Tray Build so marketing operations can
    change the provider order, the cache freshness window or the timeout in
    the visual canvas. Those are commercial decisions, and they should not
    need a coding assistant.

What it connects to

Enrichment sits between the form and the CRM, and it should touch as little as possible in between.

Clearbit

Primary firmographics from the email domain: company, size, industry, location.

Reads

ZoomInfo

Fallback coverage where the primary misses, which is most often outside North America and below fifty employees.

Reads

Salesforce

Write enriched fields with their provenance, without touching anything a person entered.

Reads and writes

Marketo

Take the form fill at source and hand back the enriched record for scoring.

Reads and writes

Snowflake

Hold the enrichment cache and the coverage history, so provider decisions are made on measured data.

Reads and writes

Slack

Alert when the timeout rate or the miss rate crosses a threshold, because a provider degrading quietly looks like a routing problem.

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

Enrichment costs money per call and sits in the critical path. Both of those need watching.

It runs where production runs, not on a laptop

Retries, backoff and background completion execute on the same engine, so a provider having a slow morning does not become a queue of unrouted leads.

Spend is visible

Calls, cache hits and cost per enriched lead reported continuously. Enrichment overspend is usually discovered at renewal, which is too late.

Managed credentials, not secrets in a config file

Every provider key is an authentication in your workspace. Enrichment keys are metered and expensive, so they should never sit in a file somebody can copy.

Ops owns the provider order

Chain order, cache window and timeout open in Tray Build, so the team holding the contract can change them.

Degradation is caught early

Alert on timeout rate and miss rate. A provider getting slower looks exactly like a routing problem until somebody checks.

Questions people ask

Why cache enrichment results?

Because the same company arrives repeatedly from different people. Enriching one domain forty times a month is the most common reason an enrichment budget runs out early.

Should enrichment block routing?

No. Set a hard timeout of a few seconds and route on whatever data exists, then complete enrichment in the background. A lead held overnight waiting for a field is worse than a lead routed without it.

Why use more than one provider?

Because coverage differs by geography and company size. The second provider costs less than the leads the first one silently misses, particularly outside North America.

Should enrichment overwrite existing values?

Never a value a person entered, and never a populated field with an empty one. A rep who corrected the employee count knows something the provider does not.

Can marketing operations change providers without engineering?

Yes. The provider chain, cache freshness and timeout open in Tray Build, so the team that owns the contract owns the settings.

Vibe-coding app guides

Vibe-code an entire app with Helix

This moves the data between systems. It does not give anybody a screen to work in. Build that app in Claude Code, Codex or Cursor, then deploy and run it governed on Tray Helix. Same kind of guide, same kind of prompts.

How to build an account research app (opens helix.tray.ai in a new tab)

Further reading

Background on the same subject, for the case rather than the build.

Last reviewed September 2026.