How to Build a CRM Enrichment Workflow That Stays Up to Date

Most CRM enrichment workflows break after initial setup. Learn how to build one that detects changes and re-enriches automatically, with API code and field mapping.

Published

Apr 25, 2026

Written by

Manmohit Grewal

Reviewed by

Nithish

Read time

7

minutes

Most CRM enrichment workflows run a one-time pass, fill in missing fields, and declare the project done. A few months later, the same records are out of date again because people changed jobs, companies got acquired, and phone numbers went dead. One GTM team we spoke with described their enrichment trigger as "book assignments," meaning a rep had to manually request a data refresh before working an account. Nothing happened between these assignments.

The harder problem is keeping your CRM fresh after the initial pass without re-enriching every record on a fixed schedule or waiting for someone to notice the data is wrong.

This guide covers both problems as separate architectural decisions, starting with how to backfill your existing CRM with a CRM enrichment API, then how to build an event-driven system that re-enriches individual records only when something actually changes. You can sign up for Crustdata's free tier (100 credits included) to follow along with the code examples.

Why CRM Data Decays (And Which Fields Decay Fastest)

B2B contact data decays at roughly 22.5% per year, according to industry benchmarks tracked by Prospeo. The same research breaks this down by field type, and the variation is significant. Work emails decay at 20 to 30% annually as people change jobs and old addresses bounce. Job titles shift at 15 to 25% as people get promoted, move laterally, or leave, while direct phone numbers change at 15 to 20% and mobile numbers remain the most stable at 5 to 10%.

These differences matter for your enrichment architecture because re-enriching every field on the same 90-day schedule means over-refreshing stable fields (wasting credits) and under-refreshing volatile ones (missing job changes that happened weeks ago). One professional services firm we spoke with had contact data up to 15 years out of date across 13,000 records. Their team described being "very poor at tracking when someone's gone from one place to another." They had no system in place to detect those changes, and the CRM became less useful with each passing quarter.

The root problem is that most enrichment setups treat enrichment as a project with a start and end date, when it needs to be an ongoing system with its own triggers and refresh logic.

Three CRM Enrichment Architectures (And When To Use Each)

CRM enrichment workflows generally fall into one of three architectural patterns, and understanding which one you need, and when to combine them, shapes everything that follows.

Architecture

How it works

When to use

Cost profile

Freshness

Batch backfill

Enrich all records in a single pass using a bulk API or dataset

Initial CRM cleanup, post-acquisition data merge, new data provider onboarding

Low per-record (cached/in-DB lookups)

Point-in-time only

Scheduled re-enrichment

Re-enrich all records (or a segment) on a fixed cadence (weekly, monthly, quarterly)

Scoring products where 30-day-old data is acceptable, low-priority account tiers

Predictable but wasteful (re-enriches unchanged records)

Depends on cadence

Event-driven re-enrichment

Monitor records for changes via webhooks/watchers, re-enrich only when a change fires

High-priority accounts, champion tracking, deal-sourcing pipelines

Lowest total cost (enriches only what changed)

Near real-time

Most teams we spoke with started with batch backfill, then tried to maintain freshness with scheduled re-enrichment, and eventually realized the economics and coverage were wrong. One founder building a CRM enrichment engine said he wanted to "push a URL to an API and find out where they work now" instead of waiting for a quarterly refresh to tell him a contact left three months ago.

The teams that solved this combined batch backfill for the initial cleanup with event-driven re-enrichment for ongoing freshness on their highest-value records, plus a lightweight quarterly scheduled pass for the rest. The architecture section below walks through how to build each phase.

If you already have a clean CRM and just need the ongoing freshness system, skip to Phase 2. For a deeper comparison of the first two patterns, see Crustdata's real-time vs batch enrichment guide.

Build Phase 1: Backfill Your Existing CRM

The backfill phase is a one-time operation that fills missing fields across your existing CRM records. The goal is completeness at the lowest possible cost per record.

Step 1: Export your CRM data and identify starting identifiers

Pull your full contact and company list from Salesforce, HubSpot, or whatever CRM you use. For each record, identify the strongest available identifier, whether that is a company domain, email address, or profile URL.

Step 2: Enrich company and people records via the API

The Company Enrichment API supports two retrieval modes. Real-time enrichment (5 credits per company) fetches live data with up to 10-minute latency. In-database enrichment (1 credit per company) returns data from Crustdata's pre-crawled database, refreshed on a rolling basis. Which mode you choose depends on your freshness requirements and budget.

Direct API path (Python):

import requests

API_TOKEN = "your_api_token"
BASE_URL = "https://api.crustdata.com"
HEADERS = {"Authorization": f"Token {API_TOKEN}"}

def enrich_company(domain, realtime=True):
    """Enrich a company record by domain."""
    params = {
        "company_domain": domain,
        "fields": "headcount,funding_and_investment,taxonomy"
    }
    if realtime:
        params["enrich_realtime"] = "true"

    resp = requests.get(
        f"{BASE_URL}/screener/company",
        headers=HEADERS,
        params=params
    )
    return resp.json() if resp.status_code == 200 else None
import requests

API_TOKEN = "your_api_token"
BASE_URL = "https://api.crustdata.com"
HEADERS = {"Authorization": f"Token {API_TOKEN}"}

def enrich_company(domain, realtime=True):
    """Enrich a company record by domain."""
    params = {
        "company_domain": domain,
        "fields": "headcount,funding_and_investment,taxonomy"
    }
    if realtime:
        params["enrich_realtime"] = "true"

    resp = requests.get(
        f"{BASE_URL}/screener/company",
        headers=HEADERS,
        params=params
    )
    return resp.json() if resp.status_code == 200 else None
import requests

API_TOKEN = "your_api_token"
BASE_URL = "https://api.crustdata.com"
HEADERS = {"Authorization": f"Token {API_TOKEN}"}

def enrich_company(domain, realtime=True):
    """Enrich a company record by domain."""
    params = {
        "company_domain": domain,
        "fields": "headcount,funding_and_investment,taxonomy"
    }
    if realtime:
        params["enrich_realtime"] = "true"

    resp = requests.get(
        f"{BASE_URL}/screener/company",
        headers=HEADERS,
        params=params
    )
    return resp.json() if resp.status_code == 200 else None

Set realtime=True for live data or realtime=False for the lower-cost in-database lookup. For teams backfilling thousands of records on a budget, the in-database mode can reduce costs significantly. If you are backfilling 10,000 company records using in-database lookups, you spend 10,000 credits. The same backfill with real-time enrichment would cost 50,000 credits. The Company Enrichment API documentation covers all the fields enriched by Crustdata's APIs.

MCP path (zero-code option): A Claude Code agent with Crustdata's MCP server configured can run the same backfill by prompting: "Enrich these 500 companies by domain and output a CSV with the results." The MCP server wraps the same REST API endpoints, so the data is identical. The agent handles batching and error recovery. This works well for one-time backfill jobs where you do not need a permanent script.

Step 3: Process people records

For contact enrichment, the People Enrichment API works the same way. Pass a profile URL or email, get back 90+ fields including employer, title, work history, skills, and optional verified business email. Use the People Enrichment API for the full field list.

The backfill phase gives you a clean baseline. The next section covers how to keep it that way.

Build Phase 2: Keep It Fresh With Event-Driven Re-Enrichment

Instead of re-enriching all records on a quarterly schedule, you set up watchers that monitor your highest-priority records, fire a webhook when something changes, and re-enrich only the records that actually need it.

Step 1: Decide which changes trigger a re-enrichment

Which changes matter depends on your workflow. A sales team tracking champion job changes cares about employer and title shifts, while an ops team prioritizing target accounts watches for headcount growth and funding events that signal the account is ready to buy.

Map your workflow to the change types that matter:

Workflow

Changes to watch

Why it matters

Champion tracking

Job change, promotion

Your buyer moved. Re-enrich to get their new company and contact info

Account prioritization

Headcount growth, funding event

The account just became higher priority. Refresh firmographics

Pipeline hygiene

Title change, company acquisition

The contact's context changed. Update CRM before next touchpoint

Step 2: Set up watchers on high-priority records

The Watcher API creates a server-side monitor that checks for changes and pushes a webhook when one is detected, so you do not need to poll the enrichment API yourself.

Direct API path (Python):

def create_people_watcher(profile_urls, webhook_url):
    """Watch a list of people for job changes and push to your webhook."""
    resp = requests.post(
        f"{BASE_URL}/screener/person/watch",
        headers=HEADERS,
        json={
            "public_urls": profile_urls,
            "webhook_url": webhook_url,
            "event_types": ["job_change", "promotion"]
        }
    )
    return resp.json()

# Watch your top 200 champions
champions = ["https://profile.com/example-1", "https://profile.com/example-2"]
create_people_watcher(champions, "https://your-app.com/webhooks/enrichment")
def create_people_watcher(profile_urls, webhook_url):
    """Watch a list of people for job changes and push to your webhook."""
    resp = requests.post(
        f"{BASE_URL}/screener/person/watch",
        headers=HEADERS,
        json={
            "public_urls": profile_urls,
            "webhook_url": webhook_url,
            "event_types": ["job_change", "promotion"]
        }
    )
    return resp.json()

# Watch your top 200 champions
champions = ["https://profile.com/example-1", "https://profile.com/example-2"]
create_people_watcher(champions, "https://your-app.com/webhooks/enrichment")
def create_people_watcher(profile_urls, webhook_url):
    """Watch a list of people for job changes and push to your webhook."""
    resp = requests.post(
        f"{BASE_URL}/screener/person/watch",
        headers=HEADERS,
        json={
            "public_urls": profile_urls,
            "webhook_url": webhook_url,
            "event_types": ["job_change", "promotion"]
        }
    )
    return resp.json()

# Watch your top 200 champions
champions = ["https://profile.com/example-1", "https://profile.com/example-2"]
create_people_watcher(champions, "https://your-app.com/webhooks/enrichment")

When one of these people changes jobs, the watcher fires a webhook to your endpoint. Your handler re-enriches that specific record and writes the updated data back to your CRM. The full Watcher API reference covers company watchers, people watchers, and event watchers.

Set different cadences by account tier. The watcher lets you configure how frequently it checks for changes, so you can tier your coverage based on account priority. For Tier 1 accounts (active deals, high-expansion potential), set the watcher to check daily or in real-time so your team always has the latest context before a call. For Tier 2 accounts, a weekly cadence catches most changes without burning through credits. For Tier 3 and the long tail, a monthly check is usually enough. This tiered approach gives you near-real-time coverage where it matters most while keeping costs manageable across your full CRM.

MCP path: With Crustdata's MCP server, you can prompt a Claude Code agent: "Set up a watcher for these 200 Tier 1 contacts with daily checks. When any of them change jobs, re-enrich the record and update HubSpot via the HubSpot API." The agent configures the watcher, writes the webhook handler, and sets up the CRM write-back.

Step 3: Handle the webhook and write back to CRM

When the watcher fires, your webhook handler receives a payload describing what changed. The handler should:

  1. Parse the change event (who changed, what changed)

  2. Call the People Enrichment API to get the full updated profile

  3. Map the enriched fields to your CRM schema (see field mapping table below)

  4. Write back to Salesforce/HubSpot via their respective APIs

  5. Log the change for audit

With this approach, your CRM updates itself when something changes in the real world, rather than waiting for someone to remember to run a batch job. One AI SDR platform we spoke with described their previous workflow as "you manually trigger it once, does all the API calls, fills it in, but then it doesn't listen to things changing." The watcher approach eliminates that gap.

For teams managing enrichment costs, event-driven re-enrichment is significantly cheaper than scheduled full re-enrichment. If you watch 5,000 contacts and 3% change in a given month (150 records), you re-enrich 150 records instead of 5,000, saving the cost and API calls of 4,850 unnecessary lookups.

Crustdata's data enrichment solution page includes pre-built patterns for Salesforce and HubSpot write-back.

Map Enriched Data Back To Your CRM

The enrichment call returns structured JSON. Your CRM expects specific fields. The mapping table below covers the most common fields for Salesforce and HubSpot.

Enrichment API field

Salesforce field

HubSpot property

Notes

company_name

Account.Name

company

Match on domain first to avoid duplicates

employee_count

Account.NumberOfEmployees

numberofemployees

Use employee_count_range for segment bucketing

hq_country

Account.BillingCountry

country

API returns ISO 3-alpha codes. Map to CRM display names

industries

Account.Industry

industry

API may return multiple. Pick primary or map to custom multi-select

total_funding_usd

Account.Custom_Funding__c

Custom property

Create a custom field. No native CRM field for this

current_title

Contact.Title

jobtitle

Overwrite only if enrichment is newer than last CRM update

current_company_name

Contact.Account (lookup)

company

If changed, this is a job change. Flag for review

verified_email

Contact.Email

email

Only overwrite if confidence is higher than existing

headcount_growth_6m

Custom field

Custom property

Useful for scoring. No native field

Recency validation rule: Before overwriting a CRM field, compare the enrichment timestamp against the field's last-modified date in the CRM. If the CRM field was updated more recently (by a rep, by another system, or by a previous enrichment pass), keep the CRM value. This prevents a common failure mode where a scheduled enrichment pass overwrites a rep's manual correction with older data.

Parent-child account resolution: When enriching company records, the API may return a parent company (e.g., the holding company) when the CRM record refers to a subsidiary. Match on domain rather than company name to avoid collapsing subsidiaries into parent records. Teams with complex account hierarchies should add a domain-match validation step before writing back.

What Changes When Your Enrichment Workflow Listens For Changes

When your enrichment workflow listens for changes and re-enriches automatically, reps no longer spend hours verifying whether a contact still works at the company listed in Salesforce. Pipeline reports reflect reality because the underlying records update when something happens, rather than lagging by a quarter. Your enrichment spend drops too, because you only pay for records that actually changed.

Build the backfill first to get a clean baseline. Then wire up watchers on your highest-value records. The combination gives you a CRM that stays accurate without depending on quarterly cleanup projects or manual data entry.

Sign up for Crustdata's free tier (100 credits included) to run your first backfill, or book a demo if you want to walk through the architecture for your specific CRM and workflow.

Data

Delivery Methods

Solutions