VendorStacks
← All posts
Engineering7 min read

Enriching CRM Records with Tech Stack Data: A Technical Implementation Guide

A step-by-step guide to automatically enriching Salesforce, HubSpot, and other CRM records with real-time tech stack data using the VendorStacks API.

Stax
VendorStacks research desk

Why Enrich CRM Records with Tech Stack Data

Sales and marketing teams make better decisions when they know what technologies their prospects and customers use. A prospect using Stripe tells you they handle payments. A company running OpenAI or Anthropic in production signals AI maturity. Snowflake usage indicates sophisticated data infrastructure.

But manually researching tech stacks doesn't scale, and most enrichment providers rely on self-reported data or stale datasets. This guide shows how to programmatically enrich CRM records with deterministic tech stack data extracted from public web evidence.

The Technical Approach

The VendorStacks API provides two endpoints that matter for CRM enrichment:

  1. GET /v1/check?url=DOMAIN — Returns the vendor stack for a company domain
  2. GET /v1/company/{domain} — Returns cached results without triggering a new scan

Both endpoints return vendor data across 24 categories (payments, ai_ml, crm_sales, cloud_infra, analytics_data, etc.) with the specific evidence row that sourced each vendor.

The key architectural decision: do you enrich on-demand or batch-process records?

Pattern 1: On-Demand Enrichment in Salesforce

Salesforce allows custom Apex code to call external APIs. This pattern enriches records when a rep views or updates them.

Step 1: Create a Custom Field Set

In Salesforce Setup, create custom fields on the Account or Lead object:

  • Tech_Stack_Payments__c (Text Area Long)
  • Tech_Stack_AI_ML__c (Text Area Long)
  • Tech_Stack_Cloud_Infra__c (Text Area Long)
  • Tech_Stack_Last_Updated__c (DateTime)
  • Uses_Stripe__c (Checkbox)
  • Uses_OpenAI__c (Checkbox)

Add fields for categories and specific vendors that matter to your ICP.

Step 2: Store Your API Key in Named Credentials

Go to Setup → Named Credentials and create a credential for https://api.vendorstacks.com with your API key in the Authorization header as Bearer vr_live_...

Step 3: Write the Apex Enrichment Class

public class VendorStackEnricher {
    @future(callout=true)
    public static void enrichAccount(Id accountId) {
        Account acc = [SELECT Website FROM Account WHERE Id = :accountId LIMIT 1];
        if (String.isBlank(acc.Website)) return;
        
        String domain = normalizeDomain(acc.Website);
        
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:VendorStacks/v1/check?url=' + EncodingUtil.urlEncode(domain, 'UTF-8'));
        req.setMethod('GET');
        
        Http http = new Http();
        HttpResponse res = http.send(req);
        
        if (res.getStatusCode() == 200) {
            Map<String, Object> data = (Map<String, Object>)JSON.deserializeUntyped(res.getBody());
            
            if ((Boolean)data.get('found')) {
                Map<String, Object> vendorStack = (Map<String, Object>)data.get('vendor_stack');
                
                acc.Tech_Stack_Payments__c = formatVendors((List<Object>)vendorStack.get('payments'));
                acc.Tech_Stack_AI_ML__c = formatVendors((List<Object>)vendorStack.get('ai_ml'));
                acc.Tech_Stack_Cloud_Infra__c = formatVendors((List<Object>)vendorStack.get('cloud_infra'));
                acc.Tech_Stack_Last_Updated__c = Datetime.now();
                
                // Set boolean flags for key vendors
                acc.Uses_Stripe__c = containsVendor((List<Object>)vendorStack.get('payments'), 'Stripe');
                acc.Uses_OpenAI__c = containsVendor((List<Object>)vendorStack.get('ai_ml'), 'OpenAI');
                
                update acc;
            }
        }
    }
    
    private static String formatVendors(List<Object> vendors) {
        if (vendors == null || vendors.isEmpty()) return '';
        List<String> names = new List<String>();
        for (Object v : vendors) {
            names.add((String)((Map<String, Object>)v).get('name'));
        }
        return String.join(names, ', ');
    }
    
    private static Boolean containsVendor(List<Object> vendors, String vendorName) {
        if (vendors == null) return false;
        for (Object v : vendors) {
            if (vendorName.equals((String)((Map<String, Object>)v).get('name'))) {
                return true;
            }
        }
        return false;
    }
    
    private static String normalizeDomain(String website) {
        return website.replaceAll('^https?://', '').replaceAll('/$', '').toLowerCase();
    }
}

Step 4: Trigger Enrichment from a Flow or Process

Create a Salesforce Flow that calls the Apex method when an Account is created or when Website changes. Use the "Apex Action" element to invoke VendorStackEnricher.enrichAccount() with the record ID.

Pattern 2: Batch Enrichment in HubSpot

HubSpot's architecture favors external batch processing via workflows and custom properties.

Step 1: Create Custom Properties

In HubSpot Settings → Properties, create company properties:

  • tech_stack_payments (Multiple checkboxes: Stripe, PayPal, Square, etc.)
  • tech_stack_ai_ml (Multiple checkboxes: OpenAI, Anthropic, Cohere, etc.)
  • tech_stack_cloud (Multiple checkboxes: AWS, Google Cloud, Azure, etc.)
  • tech_stack_enriched_at (Date picker)

Step 2: Build a Node.js Enrichment Worker

const axios = require('axios');

const VENDORSTACKS_KEY = process.env.VENDORSTACKS_API_KEY;
const HUBSPOT_KEY = process.env.HUBSPOT_PRIVATE_APP_KEY;

async function enrichCompanies() {
  // Fetch companies without recent enrichment
  const companiesRes = await axios.get(
    'https://api.hubapi.com/crm/v3/objects/companies',
    {
      params: {
        limit: 100,
        properties: 'domain,tech_stack_enriched_at',
      },
      headers: { Authorization: `Bearer ${HUBSPOT_KEY}` },
    }
  );

  const companies = companiesRes.data.results.filter(c => {
    const domain = c.properties.domain;
    const lastEnriched = c.properties.tech_stack_enriched_at;
    return domain && (!lastEnriched || isStale(lastEnriched));
  });

  for (const company of companies) {
    await enrichCompany(company);
    await sleep(100); // Rate limiting
  }
}

async function enrichCompany(company) {
  const domain = company.properties.domain;

  try {
    const res = await axios.get(
      `https://api.vendorstacks.com/v1/check?url=${encodeURIComponent(domain)}`,
      { headers: { Authorization: `Bearer ${VENDORSTACKS_KEY}` } }
    );

    if (!res.data.found) return;

    const stack = res.data.vendor_stack;
    const properties = {
      tech_stack_payments: extractNames(stack.payments),
      tech_stack_ai_ml: extractNames(stack.ai_ml),
      tech_stack_cloud: extractNames(stack.cloud_infra),
      tech_stack_enriched_at: new Date().toISOString().split('T')[0],
    };

    await axios.patch(
      `https://api.hubapi.com/crm/v3/objects/companies/${company.id}`,
      { properties },
      { headers: { Authorization: `Bearer ${HUBSPOT_KEY}` } }
    );

    console.log(`Enriched ${domain}`);
  } catch (err) {
    console.error(`Failed to enrich ${domain}:`, err.message);
  }
}

function extractNames(vendors) {
  if (!vendors || vendors.length === 0) return [];
  return vendors.map(v => v.name);
}

function isStale(dateStr) {
  const enrichedDate = new Date(dateStr);
  const daysSince = (Date.now() - enrichedDate) / (1000 * 60 * 60 * 24);
  return daysSince > 30;
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

enrichCompanies();

Run this script on a cron (daily or weekly). Deploy it on a scheduled worker in Render, Railway, or AWS Lambda with EventBridge.

Pattern 3: Real-Time Enrichment via Zapier or Make

For teams without engineering resources, no-code platforms work well for simple enrichment.

Zapier Configuration

  1. Trigger: New company in HubSpot (or Salesforce, Pipedrive, etc.)
  2. Action: HTTP Request (Webhooks by Zapier)
  • Method: GET
  • URL: https://api.vendorstacks.com/v1/check?url={{domain}}
  • Headers: Authorization: Bearer vr_live_...
  1. Action: Update company in HubSpot
  • Map response fields vendor_stack.payments, vendor_stack.ai_ml, etc. to custom properties

This pattern triggers on every new record. For existing records, use a Filter step to only enrich companies where the enrichment date is older than 30 days.

Handling Edge Cases

Domains Without Evidence

When found: false, the API returns no vendor data. This does NOT mean the company uses nothing — it means we found no public evidence. Handle this gracefully:

if (!res.data.found) {
  // Option 1: Skip the record
  return;
  
  // Option 2: Mark as "no public evidence"
  properties.tech_stack_status = 'no_evidence';
}

You are NOT charged credits for lookups that return found: false.

Rate Limiting and Cost Control

Each successful lookup costs 1 credit. A batch job enriching 1,000 companies uses 1,000 credits (assuming all return results). The $50 pack provides 6,000 credits — enough for 6,000 successful lookups.

Add rate limiting in batch scripts:

for (const company of companies) {
  await enrichCompany(company);
  await sleep(200); // 5 requests/second max
}

The API does not enforce rate limits, but adding artificial delays prevents overwhelming downstream CRM APIs.

Using the Company Endpoint for Cached Data

The /v1/check endpoint triggers a scan if the domain hasn't been indexed recently. For large batches, use /v1/company/{domain} first to check for cached results:

let res = await axios.get(
  `https://api.vendorstacks.com/v1/company/${domain}`,
  { headers: { Authorization: `Bearer ${VENDORSTACKS_KEY}` } }
);

if (!res.data.vendor_stack) {
  // No cached data; trigger a fresh scan
  res = await axios.get(
    `https://api.vendorstacks.com/v1/check?url=${domain}`,
    { headers: { Authorization: `Bearer ${VENDORSTACKS_KEY}` } }
  );
}

The /v1/company endpoint never triggers scans and costs 0 credits if no data exists.

Practical Use Cases

Sales Prioritization

Enrich inbound leads and score them based on tech stack fit. A company using Stripe, Snowflake, and AWS scores higher for a B2B SaaS vendor intelligence tool than one with no detectable vendors.

Competitive Intelligence

Track which customers use competitor products. If you sell a Salesforce alternative, query for accounts where vendor_stack.crm_sales contains "Salesforce" and prioritize outreach.

Customer Success Insights

Enrich existing customer records to understand their infrastructure. A customer using Snowflake might be ready for a data pipeline integration. One using OpenAI might want AI feature add-ons.

Real-World Vendor Distribution

Across the 1,000 companies in the VendorStacks index:

  • 340 use AWS (cloud_infra)
  • 292 use Google Analytics (analytics_data)
  • 269 use Stripe (payments)
  • 213 use OpenAI (ai_ml)
  • 175 use Anthropic (ai_ml)
  • 134 use HubSpot (crm_sales)
  • 132 use Salesforce (crm_sales)

These distributions inform ICP targeting. If your product integrates with Stripe, you have 269 companies in-index to prospect.

Monitoring and Debugging

Log API responses during enrichment to track credit usage and failure rates:

console.log({
  domain,
  found: res.data.found,
  credits_used: res.data.credits_used,
  credit_balance: res.data.credit_balance,
  vendors_found: res.data.vendor_stack ? Object.keys(res.data.vendor_stack).length : 0,
});

Check your balance programmatically:

curl -H "Authorization: Bearer vr_live_..." \
  https://api.vendorstacks.com/v1/balance

The response includes credit_balance and recent usage.

Next Steps

Start with a small batch (10-20 records) to validate the integration. Verify that vendor data appears correctly in your CRM fields. Then scale to full enrichment.

For high-volume use cases (10,000+ companies), consider the $250 pack (35,000 credits) and implement the cached-first lookup pattern to minimize costs.

The technical foundation is simple: domain → API → structured vendor data → CRM fields. The value comes from operationalizing that data in sales workflows, lead scoring, and customer segmentation.

About the author

Stax is the pangolin who fronts the VendorStacks research desk — a fitting mascot for a company that reads layered stacks for a living. Posts under this byline are written by the VendorStacks team.

VendorStacks is the subprocessor disclosure data API — structured vendor stacks with quoted evidence, 25 free credits to start.

Get an API key