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, BlogReal-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
| Operation | Endpoint | Credits |
|---|---|---|
| Full brand data (Context.dev shape) | GET /v1/brand/retrieve | 10 |
| Lightweight profile | GET /v1/brand/retrieve-simplified | 10 |
| NAICS classification (optional) | GET /v1/brand/naics | 5 |
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.
OrsaAPIErrorwithstatus: 404means there’s no brand record for that domain yet — the first/retrievecall kicks off an inline extract, so calling once more will usually succeed.