Integrating Multiple CRMs: A Tale of Salesforce, HubSpot & Dynamics

by Hamzah Ejaz, Software Engineer

At ClientPoint, I architected integrations with Salesforce, HubSpot, and Microsoft Dynamics, automating data synchronization for 2,500+ customer records. Here's how we built a robust, scalable integration layer.

The Challenge

Sales teams were losing hours to manual data entry across multiple systems. We needed to:

  • Sync contacts, deals, and activities bidirectionally
  • Handle conflicts and data inconsistencies
  • Maintain real-time updates
  • Support custom fields and complex data structures
  • Ensure data integrity across platforms

Architecture Overview

Unified Integration Layer

// Abstract CRM interface
interface CRMProvider {
  authenticate(): Promise<void>
  syncContacts(contacts: Contact[]): Promise<SyncResult>
  webhookHandler(event: WebhookEvent): Promise<void>
  mapFields(source: any, target: string): any
}

class SalesforceCRM implements CRMProvider {
  // Salesforce-specific implementation
}

class HubSpotCRM implements CRMProvider {
  // HubSpot-specific implementation
}

Key Integration Patterns

1. Bidirectional Sync with Conflict Resolution

async function syncRecord(record: Record, source: CRM, target: CRM) {
  const lastModified = await getLastModified(record.id)

  if (record.updatedAt > lastModified.target) {
    // Source is newer - sync to target
    await target.update(record)
  } else if (record.updatedAt < lastModified.target) {
    // Target is newer - sync from target
    const targetRecord = await target.get(record.id)
    await source.update(targetRecord)
  } else {
    // Conflict - use business rules
    const resolved = await resolveConflict(record, lastModified)
    await syncBoth(resolved, source, target)
  }
}

2. Webhook Management

app.post('/webhooks/salesforce', async (req, res) => {
  const event = req.body

  // Validate webhook signature
  if (!validateSignature(event, req.headers['x-sf-signature'])) {
    return res.status(401).send('Invalid signature')
  }

  // Queue for processing
  await queue.add('crm-sync', {
    source: 'salesforce',
    event,
    timestamp: Date.now()
  })

  res.status(200).send('Accepted')
})

3. Field Mapping Engine

const fieldMappings = {
  salesforce: {
    'Email': 'email',
    'Phone': 'phone',
    'Company__c': 'company',
  },
  hubspot: {
    'email': 'email',
    'phone': 'phone',
    'company': 'company',
  }
}

function mapFields(data: any, sourceCRM: string, targetCRM: string) {
  const mapped = {}
  const sourceMap = fieldMappings[sourceCRM]
  const targetMap = fieldMappings[targetCRM]

  for (const [sourceField, commonField] of Object.entries(sourceMap)) {
    if (data[sourceField]) {
      const targetField = Object.keys(targetMap).find(
        key => targetMap[key] === commonField
      )
      if (targetField) {
        mapped[targetField] = data[sourceField]
      }
    }
  }

  return mapped
}

Production Learnings

Rate Limiting Strategies

class RateLimitedAPI {
  private queue: PQueue

  constructor(requestsPerMinute: number) {
    this.queue = new PQueue({
      interval: 60000,
      intervalCap: requestsPerMinute,
    })
  }

  async call(fn: () => Promise<any>) {
    return this.queue.add(fn)
  }
}

const salesforceAPI = new RateLimitedAPI(100) // 100 req/min
const hubspotAPI = new RateLimitedAPI(150)    // 150 req/min

Error Handling & Retries

async function syncWithRetry(operation: () => Promise<any>, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation()
    } catch (error) {
      if (attempt === maxRetries) throw error

      const delay = Math.pow(2, attempt) * 1000 // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, delay))

      logger.warn(`Retry attempt ${attempt} after error:`, error.message)
    }
  }
}

Data Validation

const ContactSchema = z.object({
  email: z.string().email(),
  phone: z.string().optional(),
  firstName: z.string().min(1),
  lastName: z.string().min(1),
  company: z.string().optional(),
})

async function validateAndSync(contact: unknown) {
  try {
    const validated = ContactSchema.parse(contact)
    await syncContact(validated)
  } catch (error) {
    logger.error('Validation failed:', error)
    await notifyAdmin(contact, error)
  }
}

Performance Optimizations

Batch Processing

async function batchSync(records: Record[], batchSize = 200) {
  const batches = chunk(records, batchSize)

  for (const batch of batches) {
    await Promise.all(batch.map(record => syncRecord(record)))
    await sleep(1000) // Rate limiting
  }
}

Caching Layer

const cache = new Redis()

async function getCachedContact(id: string) {
  const cached = await cache.get(`contact:${id}`)
  if (cached) return JSON.parse(cached)

  const contact = await fetchFromCRM(id)
  await cache.setex(`contact:${id}`, 3600, JSON.stringify(contact))

  return contact
}

Results

  • 2,500+ records synchronized automatically
  • Zero data loss with comprehensive error handling
  • 99.9% sync accuracy with conflict resolution
  • Eliminated manual entry saving 10+ hours/week per team

Key Takeaways

  1. Abstract early: Build a common interface before integrating specific CRMs
  2. Validate everything: CRMs have different validation rules - normalize data
  3. Monitor proactively: Set up alerts for sync failures
  4. Document mappings: Field mappings are business logic - maintain documentation
  5. Test edge cases: Deleted records, duplicates, and network failures need handling

Enterprise integrations are complex, but with the right patterns, they become manageable and reliable.

More articles

Building AI-Powered Meeting Intelligence: Lessons from EVA Meet

A deep dive into architecting an enterprise AI platform that combines GPT-4, Perplexity AI, and Deepgram for real-time meeting intelligence.

Read more

Full Stack MERN to AI Engineer: My Journey

How I transitioned from traditional full-stack development to AI engineering, the skills I acquired, and lessons for developers making the same journey.

Read more

Ready to Transform Your Business?

Get in touch today to learn how technology can revolutionize your operations!