Services Work Pricing FAQ Blog Jobs Book a Call

HomeBlogHow to Connect Claude Code to Your CRM

How to Connect Claude Code to Your CRM (HubSpot, Pipedrive, Salesforce)

One of the first questions I get from new clients after they see what Claude Code can do is: "Can it talk to our CRM?" The answer is yes — and it's usually simpler than they expect. Over the past year I've built CRM integrations with Claude Code for HubSpot, Pipedrive, and Salesforce. The patterns are similar across all three, and once you understand the basic flow, you can extend it to almost any CRM with an API.

This post walks through how to connect Claude Code to your CRM, sync contacts and deals in both directions, and handle the edge cases that always show up in production. I'll focus on practical implementation, not theory.

Why Connect Claude Code to Your CRM

Before we get into the how, it's worth being clear about the why. The main use cases I've built for clients fall into three categories:

  • Automated data entry — syncing form submissions, inbound leads, or enriched contact data directly into the CRM without manual CSV uploads or copy-paste
  • Deal stage automation — moving deals through your pipeline based on external triggers (payment received, contract signed, onboarding completed)
  • Custom reporting — pulling CRM data into a dashboard or export that matches your team's workflow, not the CRM's built-in reports

If your team is spending more than 2 hours a week on manual CRM updates, there's probably a Claude Code integration that can reclaim most of that time. For a Vancouver-based consulting firm I work with, automating deal stage updates cut their admin time by 40%.

The Basic Integration Pattern

Every CRM integration I've built follows the same basic structure. You need three things:

  1. API credentials from your CRM (usually an API key or OAuth token)
  2. A script that makes authenticated HTTP requests to the CRM's API
  3. A data mapping layer that translates between your internal format and the CRM's expected fields

The pattern looks like this in pseudo-code:

// 1. Authenticate
const apiKey = process.env.CRM_API_KEY;
const headers = { 'Authorization': `Bearer ${apiKey}` };

// 2. Fetch or create a contact
const contact = await fetch('https://api.crm.com/contacts', {
  method: 'POST',
  headers,
  body: JSON.stringify({
    email: 'lead@example.com',
    firstName: 'Jane',
    customField: 'value'
  })
});

// 3. Handle the response
if (contact.id) {
  console.log('Contact created:', contact.id);
}

That's the core. Everything else is refinement: error handling, retries, batch operations, conflict resolution. Let's walk through each CRM.

Connecting to HubSpot

HubSpot has one of the cleanest APIs I've worked with. Their documentation is good, and the rate limits are generous for most use cases (100 requests per 10 seconds on the free tier).

To get started, you need a private app API key from your HubSpot account. Go to Settings → Integrations → Private Apps, create a new app, and grant it the scopes you need (usually contacts:write and deals:write). Copy the key and store it in your environment variables.

Here's a working example that creates a contact with custom properties:

const HUBSPOT_KEY = process.env.HUBSPOT_API_KEY;

async function createContact(email, firstName, lastName, company) {
  const response = await fetch('https://api.hubapi.com/crm/v3/objects/contacts', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${HUBSPOT_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      properties: {
        email,
        firstname: firstName,
        lastname: lastName,
        company
      }
    })
  });

  const data = await response.json();
  return data.id;
}

For custom fields, you just add them to the properties object using their internal name (which you can find in HubSpot's property settings). If a contact with that email already exists, HubSpot returns a 409 conflict — you can either handle that by doing an update instead, or use their search API to check first.

Connecting to Pipedrive

Pipedrive is popular with small sales teams in Vancouver, and their API is almost as straightforward as HubSpot's. The main difference is that Pipedrive uses a single API token instead of OAuth, which makes authentication simpler but less secure if you're building a multi-tenant app.

Get your API token from Settings → Personal Preferences → API. Then you append it as a query parameter to every request:

const PIPEDRIVE_TOKEN = process.env.PIPEDRIVE_API_TOKEN;
const domain = 'yourcompany.pipedrive.com';

async function createDeal(title, value, personId, stageId) {
  const response = await fetch(
    `https://${domain}/api/v1/deals?api_token=${PIPEDRIVE_TOKEN}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        title,
        value,
        person_id: personId,
        stage_id: stageId
      })
    }
  );

  const data = await response.json();
  return data.data.id;
}

One quirk with Pipedrive: deals require a person (contact) to already exist, so you usually create the person first, get their ID, and then create the deal. Custom fields work the same way as HubSpot — find the field key in your settings and add it to the payload.

Connecting to Salesforce

Salesforce is more complex than HubSpot or Pipedrive, mainly because of OAuth. You need to set up a Connected App in Salesforce, go through the OAuth flow to get an access token, and refresh it when it expires. The upside is that once you have the token, the REST API is powerful and well-documented.

I'm not going to walk through the full OAuth setup here — Salesforce's docs cover it well — but the basic pattern after you have a token is similar:

const SF_TOKEN = process.env.SALESFORCE_ACCESS_TOKEN;
const SF_INSTANCE = 'https://yourinstance.salesforce.com';

async function createLead(firstName, lastName, company, email) {
  const response = await fetch(
    `${SF_INSTANCE}/services/data/v57.0/sobjects/Lead/`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${SF_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        FirstName: firstName,
        LastName: lastName,
        Company: company,
        Email: email
      })
    }
  );

  const data = await response.json();
  return data.id;
}

The main difference with Salesforce is that field names use PascalCase, and you need to know the exact object type (Lead, Contact, Opportunity) you're working with. Custom fields end with __c.

Handling Two-Way Sync

The examples above are all one-way: pushing data into the CRM. A lot of my clients also need the reverse — pulling CRM data out to feed into internal dashboards, email tools, or content systems.

Two-way sync introduces a new problem: conflicts. If a contact is updated in both your system and the CRM between syncs, which version wins? The simple answer is "last write wins," but that can lose data. The better answer is to track modification timestamps and either merge changes or flag conflicts for manual review.

In practice, most small teams don't need true two-way sync. They need one system of record (usually the CRM) and unidirectional flows in and out. That's simpler to build and maintain.

If you do need two-way sync, the pattern I use is:

  1. Pull all modified records since the last sync (using a lastModifiedDate filter)
  2. Compare timestamps between your system and the CRM
  3. Apply the newer version to the older system
  4. Log any conflicts where both versions were modified within the sync window

Error Handling and Rate Limits

CRM APIs fail. Networks time out. Rate limits get hit. Your integration needs to handle all of this gracefully, or you'll end up with missing data and frustrated users.

The three patterns I use in every integration:

  • Retry with exponential backoff — if a request fails with a 5xx error or timeout, wait 1 second and retry. If it fails again, wait 2 seconds, then 4, then 8. Give up after 5 attempts.
  • Rate limit detection — if you get a 429 response, pause for the time specified in the Retry-After header (or 60 seconds if not provided) and then resume.
  • Dead letter queue — if a record fails all retries, log it to a file or database so you can review and reprocess it manually later.

Here's a simple retry wrapper I use:

async function fetchWithRetry(url, options, maxRetries = 5) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      if (response.ok) return response;
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || 60;
        await sleep(retryAfter * 1000);
        continue;
      }
      if (response.status >= 500) {
        await sleep(Math.pow(2, i) * 1000);
        continue;
      }
      throw new Error(`HTTP ${response.status}`);
    } catch (err) {
      if (i === maxRetries - 1) throw err;
      await sleep(Math.pow(2, i) * 1000);
    }
  }
}

Real-World Example: Form Submission to CRM

The most common integration I build is form submission → CRM contact. A client has a lead gen form on their website, and they want every submission to create or update a contact in HubSpot with tags based on which form was filled out.

The flow looks like this:

  1. User submits form
  2. Webhook fires to a Claude Code endpoint
  3. Script validates the data and enriches it (e.g., adds UTM parameters, lead source)
  4. Script checks if contact exists in HubSpot by email
  5. If yes, update the existing contact and append a note. If no, create a new contact.
  6. Add the contact to a specific list based on the form type
  7. Return success or failure to the webhook caller

This entire workflow runs in under 2 seconds, and it eliminates the need for Zapier or manual data entry. A Vancouver real estate client of mine processes about 150 leads a month this way, and it's saved them roughly 6 hours of admin work per month.

When to Use Claude Code vs. a No-Code Tool

I get asked this a lot. The honest answer is: if your integration is simple and you don't need custom logic, use Zapier or Make. They're faster to set up and easier for non-technical team members to maintain.

Use Claude Code when you need:

  • Custom data transformations (e.g., parsing PDFs, scoring leads, calculating custom fields)
  • Conditional logic that's too complex for a no-code tool's if/then builder
  • Tight integration with internal tools that don't have pre-built connectors
  • Full control over error handling and retry logic

For everything else, Zapier is fine. No need to over-engineer it. If you're curious about when automation makes sense for your business, I wrote a detailed comparison here.

Getting Started

If you want to try building a CRM integration with Claude Code, start small. Pick one workflow — like "new form submission creates a HubSpot contact" — and build just that. Get it working end-to-end before you add complexity.

The usual timeline for a basic integration is 2–4 hours if you have API access and know your CRM's field structure. Most of that time is spent on testing and handling edge cases, not writing code.

If you're not sure where to start or you want to see how this could work for your specific CRM setup, I'm happy to walk through it on a call. You can book time with me here, or check the FAQ page for answers to the most common questions.

The tools exist. The APIs are documented. The question is just which part of your CRM workflow you want to automate first.

Frequently Asked

FAQ

Can Claude Code replace Zapier for CRM integrations?

For most use cases, yes. Claude Code gives you more control over error handling, complex field mapping, and conditional logic. Zapier is faster to set up for simple triggers, but Claude Code is better when you need custom transformations, multi-step workflows, or tight integration with internal tools. The trade-off is development time versus long-term flexibility.

How long does it take to build a CRM integration with Claude Code?

A basic one-way sync (e.g., new form submission → create CRM contact) takes 2–4 hours to build and test. A two-way sync with custom field mapping and conflict resolution takes 1–2 days. Most of the time goes into understanding your CRM's custom fields and business logic, not the actual coding.

Will this work with custom CRM fields and pipelines?

Yes. That's one of the main reasons to use Claude Code over a no-code tool. You can map any custom field, handle nested objects, transform data before sync, and implement business rules that match your sales process. The integration adapts to your CRM setup, not the other way around.

Work with me

Want this kind of result for your business?

I build Claude Code tools, automations, and AI systems for Vancouver businesses — usually with a working prototype in 48 hours.

Book a Free Call Read the FAQ
← All posts Book a call →