Every inbound contact, fully enriched and routed in under five minutes. I designed the Master Workflow, then rebuilt the Clay engine behind it to cut credit spend, block wrong-person matches and add one-click re-enrichment.
Every contact entering the CRM now follows one deterministic path: enrichment, customer detection, ownership assignment by traffic source, then segmentation. The full ingestion phase resolves in under five minutes, and a single Master Workflow owns the contact from creation to routing. It replaced what used to be three or four workflows running in parallel.
The practical result is that every downstream campaign workflow can finally assume it's working with complete, classified data. Existing customers never accidentally enter marketing nurture paths, because customer detection happens before routing rather than as an afterthought. And because the workflow drops contacts into a static segment at every decision point, there's a clear audit trail for any contact's journey through the system.
Then I kept going. Over the following months I rebuilt the enrichment engine behind the workflow several times: to stop a credit burn that was emptying our Clay account, to stop bad data from reaching the CRM, and to give the team ways to enrich contacts on demand. In a 100-row test after the rebuild, the main enrichment step cost under 2 credits per contact, down from about 12.7 before.
I owned this system end to end. I designed the flow, built the HubSpot workflows, properties and segments, built and rebuilt every Clay table, wrote the AI prompts, ran the credit audit, cleaned up the data the old setup had left behind, and built the internal tool the sales team now uses to import lists.
A contact entering the CRM should be a simple event. In practice, it triggered a small explosion. One workflow tried to set the lifecycle stage, another tried to enrich, another handled segmentation, and another was tied to the specific form that captured the contact. None of these workflows knew about each other. They didn't share state, they had no defined order of operations, and they'd been added over time by different people solving different problems.
The consequences were predictable. Lifecycle stages got set before enrichment finished, so they were based on incomplete data. Contacts got assigned to owners before anyone knew whether they were customers or prospects. Marketing automation enrolled contacts that should have been excluded. And because the workflows were independent, there was no single place to debug when something went wrong.
The underlying problem wasn't enrichment quality or routing logic. It was that nothing owned the contact during the moment when its identity should have been established.
Before building anything, I mapped the entire intended flow in FigJam: every branch, every condition, every state transition. This forced me to resolve ambiguity before implementation and gave me an artifact I could walk colleagues through. Building the diagram first surfaced edge cases I'd otherwise have hit in production.
The design came down to a few decisions:
When a contact is created, the Master Workflow takes ownership immediately. It adds the contact to an initial segment, sets lead status to New and lifecycle stage to Lead, then sends a webhook to Clay carrying the contact's data. The workflow then holds on a short delay, which gives HubSpot time to resolve company associations and existing deal links before any branching logic runs.
Clay enriches asynchronously and writes back to the contact record, populating person and company fields along with two AI-driven classifications: personal versus business email (too many regional domain variations to enumerate by hand), and a mapping from Clay's open industry taxonomy onto HubSpot's fixed dropdown of roughly 150 options. A custom property, GTM Enrichment Status, records where the contact sits in the pipeline as pending, complete, or failed, so the state is visible directly on the record without digging into workflow history.
After the delay, the first branch checks whether the contact belongs to an existing customer. If it does, the contact bypasses the marketing path entirely: lifecycle stage is set to Customer, lead status to Closed, and marketing contact status is removed, since there's no reason to market to an existing customer. If the contact is not a customer, the workflow assigns contact and company owner based on traffic source, then hands the contact off to whichever campaign workflow matches what they actually did, such as downloading an ebook or submitting a contact form.
HubSpot and Clay are connected directly, with no middleware between them. Most contacts are fully enriched within five minutes, depending on how long Clay takes to return data.
The Master Workflow stayed the same. What changed is everything Clay does between receiving the contact and writing it back.
How the enrichment machine works today
At one point our Clay account was close to empty. An audit showed it was burning around 5,300 credits a day, and more than half of that went to AI calls. The full pipeline cost more than 22 credits per contact.
The result: in a 100-row test after the rebuild, the main enrichment table cost under 2 credits per contact, down from about 12.7 before.
Automation covers new contacts, but people also need a manual way in. I built two.
A related clean-up shows why this matters: after one bulk import, 726 contacts were classified with the persona "Other". After fixing the enrichment logic and re-enriching them, that dropped to 100, of which 47 really are "Other".
HubSpot is the system of record and the orchestration layer. The Master Workflow, the lifecycle and lead-status logic, the segmentation, and the custom enrichment-state property all live there.
Clay handles enrichment: HubSpot sends a new contact's data to Clay, which returns enriched person and company fields plus the AI classifications, then writes back to the contact record. The two are wired together directly, without an intermediary automation tool.