GuidesUse CasesLead Enrichment

CRM Lead Enrichment

Automatically enrich CRM leads with brand data — logos, industry, social links, and company metadata — the moment they enter your pipeline.

The Problem

Sales teams waste time researching leads manually. Company logos are missing from CRM cards. Industry and size data is incomplete or stale.

The Solution

Pipe new leads through Orsa to enrich records with verified brand data in real-time or batch.

Pipeline Architecture

New Lead → Extract Domain → Orsa Brand API → Update CRM Record

                                              Logo: ✓
                                              Industries: ["SaaS", "Project Management"]
                                              Socials: Twitter, LinkedIn
                                              Links: Pricing, Careers, Blog

Real-time Enrichment

Enrich leads as they come in via webhook or form submission.

TypeScript

import Orsa from '@orsa.dev/sdk';
 
const orsa = new Orsa({ apiKey: process.env.ORSA_API_KEY! });
 
async function enrichLead(lead: { email: string; id: string }) {
  const domain = lead.email.split('@')[1];
 
  // Returns the Context.dev shape — colors / logos / socials are arrays,
  // links is a named-slot dict.
  const brand = await orsa.brand.retrieve({ domain });
 
  const twitter = brand.socials.find((s) => s.type === 'twitter')?.url;
  const linkedin = brand.socials.find((s) => s.type === 'linkedin')?.url;
 
  return {
    leadId: lead.id,
    company: brand.title,
    domain: brand.domain,
    description: brand.description,
    logo: brand.logos.find((l) => l.type === 'logo')?.url,
    industries: brand.industries,
    socials: { twitter, linkedin },
    careersPage: brand.links.careers,
    pricingPage: brand.links.pricing,
    enrichedAt: new Date().toISOString(),
  };
}

Python

from orsa import Orsa
 
orsa = Orsa(api_key=os.environ["ORSA_API_KEY"])
 
def enrich_lead(email: str, lead_id: str) -> dict:
    domain = email.split("@")[1]
    brand = orsa.brand.retrieve(domain=domain)
 
    socials = {s["type"]: s["url"] for s in (brand.socials or [])}
    logos_by_type = {l["type"]: l["url"] for l in (brand.logos or [])}
 
    return {
        "lead_id": lead_id,
        "company": brand.title,
        "domain": brand.domain,
        "logo": logos_by_type.get("logo"),
        "industries": brand.industries,
        "socials": {
            "twitter": socials.get("twitter"),
            "linkedin": socials.get("linkedin"),
        },
    }

Batch Enrichment

For existing CRM data, deduplicate by domain first and use the SDK’s built-in caching on the API side.

async function batchEnrich(leads: Array<{ email: string; id: string }>) {
  // Deduplicate domains — cuts API calls dramatically for shared employer domains.
  const domains = [...new Set(leads.map((l) => l.email.split('@')[1]))];
 
  // First pass: warm the brand cache for each unique domain. The first call per
  // domain takes a second; subsequent calls hit the cache and return instantly.
  const brandByDomain = new Map<string, Awaited<ReturnType<typeof orsa.brand.retrieve>>>();
  for (const domain of domains) {
    try {
      brandByDomain.set(domain, await orsa.brand.retrieve({ domain }));
    } catch {
      // Skip domains we can't resolve.
    }
  }
 
  // Second pass: build per-lead records from the cached brands.
  return leads
    .map((lead) => {
      const domain = lead.email.split('@')[1];
      const brand = brandByDomain.get(domain);
      if (!brand) return null;
      return {
        leadId: lead.id,
        company: brand.title,
        domain,
        logo: brand.logos.find((l) => l.type === 'logo')?.url,
        industries: brand.industries,
      };
    })
    .filter(Boolean);
}

Credit Cost

OperationEndpointCredits
Full brand data (Context.dev shape)GET /v1/brand/retrieve10
Lightweight profileGET /v1/brand/retrieve-simplified10
NAICS classification (optional)GET /v1/brand/naics5

If you only need logo + primary color + industries, use retrieveSimplified for a slimmer payload. Either way, the API caches per domain — duplicate calls within the TTL are free.

CRM Integration Examples

HubSpot (via webhook)

// Webhook handler for new HubSpot contacts
app.post('/webhooks/hubspot', async (req, res) => {
  const { email, hs_object_id } = req.body;
 
  const enrichment = await enrichLead({ email, id: hs_object_id });
 
  await hubspot.contacts.update(hs_object_id, {
    properties: {
      company: enrichment.company,
      industry: enrichment.industries?.[0],
      website: `https://${enrichment.domain}`,
    },
  });
 
  res.json({ ok: true });
});

Salesforce (via Apex trigger)

Call your enrichment API from a Salesforce trigger or flow, then update the Lead record with the returned data.

Tips

  • Deduplicate by domain before batch enrichment — multiple leads from the same company only need one API call.
  • Store the domain alongside the enrichment so you can refresh data later without re-extracting emails.
  • Rate limit your batch processing to stay within your plan limits. Process in chunks of 10-50 with delays between batches.
  • Handle 404s gracefully. OrsaAPIError with status: 404 means there’s no brand record for that domain yet — the first /retrieve call kicks off an inline extract, so calling once more will usually succeed.