How to Build a Sales Lead Generation Product with Unified
September 16, 2025
Unified.to makes it easy for developers to build lead generation applications that integrate with your customers' CRM of choice. With a single integration, you can connect to dozens of CRMs (like HubSpot, Salesforce, Pipedrive, and more), insert leads, enrich them, and sync all sales data—without building custom connectors for each platform.
Prerequisites
- Node.js (v18+)
- Unified account with CRM integration enabled (e.g., HubSpot)
- Unified API key
- Your customer's CRM connection ID (from Unified's embedded auth flow)
- (Optional) Enrichment provider connection (e.g., Clearbit, ZoomInfo)
Step 1: Setting up your project
mkdir leadgen-demo
cd leadgen-demo
npm init -y
npm install @unified-api/typescript-sdk dotenv
Add your credentials to .env
:
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_CRM=your_customer_crm_connection_id
Step 2: Initialize the SDK
import 'dotenv/config';
import { UnifiedTo } from '@unified-api/typescript-sdk';
const { UNIFIED_API_KEY, CONNECTION_CRM } = process.env;
const sdk = new UnifiedTo({
security: { jwt: UNIFIED_API_KEY! },
});
Step 3: Add a Lead
Before adding a lead, always check if it already exists as a lead or contact (to avoid duplicates).
export async function createLead(connectionId: string, email: string, name: string) {
// Check if lead already exists
const leadsResult = await sdk.crm.listCrmLeads({
connectionId,
query: email,
limit: 1,
});
if (leadsResult.crmLeads?.length) {
return false;
}
// Check if contact already exists
const contactsResult = await sdk.crm.listCrmContacts({
connectionId,
query: email,
limit: 1,
});
if (contactsResult.crmContacts?.length) {
return false;
}
// Create the lead
const createLeadResult = await sdk.crm.createCrmLead({
connectionId,
crmLead: {
emails: [{ email }],
name,
},
});
return createLeadResult.crmLead;
}
Explanation:
- This function checks for existing leads and contacts by email.
- If neither exists, it creates a new lead in the CRM.
Step 4: Add a Contact
If you want to add a contact (instead of a lead), check for an existing contact first.
export async function createContact(connectionId: string, email: string, name: string) {
const result = await sdk.crm.listCrmContacts({
connectionId,
query: email,
limit: 1,
});
if (result.crmContacts?.length) {
return result.crmContacts[0];
}
const createResult = await sdk.crm.createCrmContact({
connectionId,
crmContact: {
emails: [{ email }],
name,
},
});
return createResult.crmContact;
}
Explanation:
- Checks for an existing contact by email.
- If not found, creates a new contact.
Step 5: Add a Company and Deal (Optional)
If you're creating a contact, you may also want to create an associated company and deal, and link them to the contact.
export async function createCompanyAndDeal(connectionId: string, contactId: string, companyName: string, website: string) {
// Check if company exists
const result = await sdk.crm.listCrmCompanies({
connectionId,
query: companyName,
limit: 1,
});
if (result.crmCompanies?.length) {
return;
}
// Create company
const createCompanyResult = await sdk.crm.createCrmCompany({
connectionId,
crmCompany: {
name: companyName,
websites: [website],
},
});
// Create deal
const createDealResult = await sdk.crm.createCrmDeal({
connectionId,
crmDeal: {
name: `${companyName}'s deal`,
},
});
// Link company and deal to contact
if (createCompanyResult.crmCompany?.id && createDealResult.crmDeal?.id) {
const createContactResult = await sdk.crm.getCrmContact({
connectionId,
id: contactId,
});
await sdk.crm.updateCrmContact({
connectionId,
id: contactId,
crmContact: {
dealIds: (createContactResult.crmContact?.dealIds || []).concat(createDealResult.crmDeal.id),
companyIds: (createContactResult.crmContact?.companyIds || []).concat(createCompanyResult.crmCompany.id),
},
});
}
}
Explanation:
- Checks for an existing company by name.
- If not found, creates a new company and a new deal.
- Links both to the contact.
Step 6: Enrich Leads/Contacts (Optional)
You can enrich your leads or contacts using enrichment providers (like Clearbit or ZoomInfo) via Unified.
export async function enrichPerson(connectionId: string, email: string) {
const result = await sdk.enrichment.enrichPerson({
connectionId,
email,
});
return result.person;
}
Explanation:
- Calls the enrichment endpoint to get additional info about a person by email.
Step 7: Putting It All Together
Here's how you might use these functions in your lead gen workflow:
async function main() {
const email = "lead@example.com";
const name = "Lead Name";
const company = "Example Inc";
const website = "https://example.com";
// 1. Enrich the lead (optional)
const enriched = await enrichPerson(CONNECTION_CRM!, email);
// 2. Create the lead
const lead = await createLead(CONNECTION_CRM!, email, name);
// 3. Create the contact (if needed)
const contact = await createContact(CONNECTION_CRM!, email, name);
// 4. Create company and deal, and link to contact
if (contact) {
await createCompanyAndDeal(CONNECTION_CRM!, contact.id, company, website);
}
console.log("Lead, contact, and company/deal created and linked!");
}
main();
Summary
- Unified lets you build a lead gen product that works with any CRM, using a single API and SDK.
- You can add leads, contacts, companies, and deals, and enrich them with third-party data.
- The example uses HubSpot, but the same code works for Salesforce, Pipedrive, and all Unified CRM integrations—just swap the connection ID.