# How to Integrate Replit with Pipedrive

- Tool: Replit
- Difficulty: Intermediate
- Time required: 20 minutes
- Last updated: March 2026

## TL;DR

To integrate Replit with Pipedrive, store your Pipedrive API token in Replit Secrets (lock icon 🔒) and call the Pipedrive REST API from your server-side code. Pipedrive uses a simple API token that authenticates all requests — no OAuth flow required. From Replit you can create deals, persons, organizations, and activities, set up webhook subscriptions for real-time event delivery, and build custom sales pipeline automation. Use Autoscale deployment for webhook receivers.

## Sales Pipeline Automation from Replit with the Pipedrive API

Pipedrive's visual pipeline is one of the most intuitive CRM interfaces for sales teams, and its REST API exposes the same data model in a clean, consistently structured way. Every core entity — deals, persons, organizations, activities, products — follows the same request and response pattern, making Pipedrive one of the easiest CRMs to build integrations against. Simple API token authentication (no OAuth flow) means you can go from zero to first API call in minutes rather than the 45-minute setup that enterprise CRMs like Dynamics 365 require.

From Replit, the most valuable Pipedrive integration patterns are lead ingestion (automatically creating deals from web forms, ads, or external tools), pipeline event automation (triggering actions in other systems when deals advance stages), and activity management (creating follow-up tasks, calls, and emails programmatically). These patterns eliminate manual data entry for your sales team and ensure every lead is captured in the pipeline without human intervention.

Pipedrive's webhook system delivers real-time events to your Replit server when deals move through stages, deals are won or lost, new persons are created, or activities are completed. This event-driven architecture lets you build rich integrations — notify Slack when a deal is won, create a contract in DocuSign when a deal moves to the closing stage, or update an accounting system when a new client is added. For webhook delivery to work reliably, your Replit app must be deployed (not running in development mode, which goes offline when you close your browser).

## Before you start

- A Replit account with a Node.js or Python Repl ready
- A Pipedrive account — free trial available at pipedrive.com
- Your Pipedrive API token from Settings → Personal Preferences → API in Pipedrive
- For webhooks: your app deployed with a stable HTTPS URL (https://yourapp.replit.app)

## Step-by-step guide

### 1. Get Your Pipedrive API Token and Store It in Replit Secrets

Pipedrive API tokens are user-specific — each Pipedrive user has their own API token that grants access to their account's data according to their permission level. For integration purposes, create a dedicated Pipedrive user account for your automation (e.g., 'Integration Bot') with appropriate permissions, and use that user's token. This way, if the token needs to be rotated, only the bot user is affected, and activities created via the API are attributed to a recognizable bot user rather than a personal account.

To find your API token: log into Pipedrive → click your avatar/name in the top right → Settings → Personal Preferences → scroll down to 'API' section or navigate to the API tab. Your token is a 40-character alphanumeric string. Copy it.

You will also need your Pipedrive company domain, which appears in your Pipedrive URL as: https://{company}.pipedrive.com. Some API calls require this subdomain.

Open your Replit project and click the lock icon (🔒) in the sidebar. Add:

PIPEDRIVE_API_TOKEN: your 40-character Pipedrive API token.

PIPEDRIVE_COMPANY_DOMAIN: your company subdomain (the part before .pipedrive.com).

Pipedrive's API token provides the same access as the user who owns it — treat it as a password. Replit's Secret Scanner will flag the token if it detects it in source code.

```
// check-pipedrive-secrets.js
const required = ['PIPEDRIVE_API_TOKEN'];
for (const key of required) {
  if (!process.env[key]) {
    throw new Error(`Missing: ${key}. Add it in Replit Secrets (lock icon 🔒).`);
  }
}
const token = process.env.PIPEDRIVE_API_TOKEN;
if (token.length !== 40) {
  console.warn(`Warning: Pipedrive API tokens are typically 40 characters. Got ${token.length}.`);
}
console.log('Pipedrive secrets configured.');
console.log('Token prefix:', token.slice(0, 8) + '...');
```

**Expected result:** PIPEDRIVE_API_TOKEN is set in Replit Secrets. The verification script confirms the token length and prints without errors.

### 2. Create Deals, Persons, and Organizations with Node.js

The Pipedrive REST API base URL is https://api.pipedrive.com/v1. Authentication is via the api_token query parameter appended to every request URL, or as a Bearer token in the Authorization header — use the header approach in production as it keeps the token out of server access logs.

Pipedrive entities have a hierarchy: Persons and Organizations are contacts; Deals are sales opportunities linked to a Person and/or Organization and placed in a pipeline stage. Always create the Person first, then the Deal linked to that Person.

The Pipedrive API consistently returns a wrapper object: { success: true, data: {...} } for single records and { success: true, data: [{...}] } for lists. Always check success before accessing data.

Custom fields in Pipedrive are stored with hash keys (e.g., 'abc123' rather than a human-readable name). Find custom field hash keys in Pipedrive Settings → Data Fields → hover over a custom field to see its key, or call GET /v1/personFields or /v1/dealFields to list all fields with their keys.

For Python examples, the pattern is identical using the requests library — the code is shown in the next step.

```
// pipedrive-client.js — Create and manage Pipedrive entities
const TOKEN = process.env.PIPEDRIVE_API_TOKEN;
const BASE_URL = 'https://api.pipedrive.com/v1';

async function pipedriveRequest(method, endpoint, body = null, params = {}) {
  const url = new URL(`${BASE_URL}${endpoint}`);
  // Optionally use header auth instead: Authorization: Bearer {token}
  url.searchParams.set('api_token', TOKEN);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));

  const options = {
    method,
    headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }
  };
  if (body) options.body = JSON.stringify(body);

  const response = await fetch(url.toString(), options);
  const data = await response.json();

  if (!data.success) {
    throw new Error(`Pipedrive error: ${data.error || JSON.stringify(data)}`);
  }
  return data.data;
}

// Create a Person (contact)
async function createPerson(name, email, phone, orgId = null) {
  return pipedriveRequest('POST', '/persons', {
    name,
    email: [{ value: email, primary: true }],
    phone: [{ value: phone, primary: true }],
    org_id: orgId
  });
}

// Create an Organization
async function createOrganization(name, address = '') {
  return pipedriveRequest('POST', '/organizations', { name, address });
}

// Create a Deal
async function createDeal(title, personId, orgId = null, stageId = null, value = null) {
  return pipedriveRequest('POST', '/deals', {
    title,
    person_id: personId,
    org_id: orgId,
    stage_id: stageId,   // null = first stage of default pipeline
    value,               // deal value in account currency
    currency: 'USD'
  });
}

// Add a note to a deal
async function addNote(dealId, content) {
  return pipedriveRequest('POST', '/notes', {
    deal_id: dealId,
    content
  });
}

// Get pipeline stages
async function getStages(pipelineId = null) {
  const params = pipelineId ? { pipeline_id: pipelineId } : {};
  return pipedriveRequest('GET', '/stages', null, params);
}

// Get deals by stage
async function getDealsByStage(stageId, status = 'open') {
  return pipedriveRequest('GET', '/deals', null, {
    stage_id: stageId,
    status  // 'open', 'won', 'lost', 'deleted', 'all_not_deleted'
  });
}

// Example: create a complete lead
(async () => {
  try {
    // 1. Create the organization
    const org = await createOrganization('Acme Corp', '123 Main St, New York, NY');
    console.log('Org created:', org.id, org.name);

    // 2. Create the person
    const person = await createPerson('Jane Smith', 'jane@acme.com', '+15551234567', org.id);
    console.log('Person created:', person.id, person.name);

    // 3. Create the deal
    const deal = await createDeal('Acme Corp — Enterprise License', person.id, org.id, null, 15000);
    console.log('Deal created:', deal.id, deal.title);

    // 4. Add a note
    await addNote(deal.id, 'Inbound lead from website contact form. Interested in annual enterprise plan.');
    console.log('Note added.');
  } catch (err) {
    console.error('Error:', err.message);
  }
})();

module.exports = { createPerson, createOrganization, createDeal, addNote, getStages, getDealsByStage, pipedriveRequest };
```

**Expected result:** Running the script creates an organization, person, and linked deal in Pipedrive. All three appear in the Pipedrive interface under their respective sections. The note is visible on the deal's detail page.

### 3. Create Pipedrive Entities with Python

The Python Pipedrive client uses the requests library (pre-installed in most Replit Python environments). Authentication appends the api_token to all request URLs or uses a Bearer header. Response parsing checks the success field before returning the data.

For Flask applications, the pipedrive request functions can be called directly from route handlers. For background jobs (e.g., syncing leads from a database every hour), call them from a scheduled task or thread.

Error handling should distinguish between Pipedrive-reported errors (success: False with an error message) and network/HTTP errors. The PipedriveError below wraps both cases with a consistent interface.

Pipedrive returns consistent data structures across entity types — create_person and create_deal follow exactly the same response pattern, making it easy to extend to any other entity type using the same pattern.

```
# pipedrive_client.py — Pipedrive REST API client for Replit
import os
import requests

TOKEN = os.environ['PIPEDRIVE_API_TOKEN']
BASE_URL = 'https://api.pipedrive.com/v1'

def pipedrive_request(method: str, endpoint: str, body: dict = None, params: dict = None) -> dict:
    """Make an authenticated Pipedrive API request."""
    url = f'{BASE_URL}{endpoint}'
    all_params = {'api_token': TOKEN}
    if params:
        all_params.update(params)

    response = requests.request(
        method, url,
        params=all_params,
        json=body,
        headers={'Content-Type': 'application/json'}
    )
    response.raise_for_status()
    data = response.json()

    if not data.get('success'):
        raise Exception(f'Pipedrive error: {data.get("error", str(data))}')

    return data['data']

def create_person(name: str, email: str, phone: str = None, org_id: int = None) -> dict:
    """Create a Person in Pipedrive."""
    payload = {
        'name': name,
        'email': [{'value': email, 'primary': True}]
    }
    if phone:
        payload['phone'] = [{'value': phone, 'primary': True}]
    if org_id:
        payload['org_id'] = org_id
    return pipedrive_request('POST', '/persons', payload)

def create_organization(name: str, address: str = None) -> dict:
    """Create an Organization in Pipedrive."""
    payload = {'name': name}
    if address:
        payload['address'] = address
    return pipedrive_request('POST', '/organizations', payload)

def create_deal(title: str, person_id: int = None, org_id: int = None,
                value: float = None, stage_id: int = None) -> dict:
    """Create a Deal in Pipedrive."""
    payload = {'title': title, 'currency': 'USD'}
    if person_id: payload['person_id'] = person_id
    if org_id: payload['org_id'] = org_id
    if value is not None: payload['value'] = value
    if stage_id: payload['stage_id'] = stage_id
    return pipedrive_request('POST', '/deals', payload)

def add_note(deal_id: int, content: str) -> dict:
    """Add a note to a deal."""
    return pipedrive_request('POST', '/notes', {'deal_id': deal_id, 'content': content})

def create_activity(deal_id: int, subject: str, activity_type: str, due_date: str) -> dict:
    """Create a follow-up activity (call, email, task) linked to a deal."""
    return pipedrive_request('POST', '/activities', {
        'deal_id': deal_id,
        'subject': subject,
        'type': activity_type,  # 'call', 'email', 'task', 'meeting'
        'due_date': due_date    # YYYY-MM-DD format
    })

if __name__ == '__main__':
    org = create_organization('Acme Corp')
    print(f'Org: {org["id"]} — {org["name"]}')

    person = create_person('Jane Smith', 'jane@acme.com', '+15551234567', org['id'])
    print(f'Person: {person["id"]} — {person["name"]}')

    deal = create_deal('Enterprise License', person['id'], org['id'], 15000.0)
    print(f'Deal: {deal["id"]} — {deal["title"]} (${deal["value"]})')
```

**Expected result:** Running the script creates an organization, person, and deal in Pipedrive via the Python client. All three entities appear in the Pipedrive web interface.

### 4. Set Up Pipedrive Webhooks to Receive Real-Time Events

Pipedrive webhooks deliver real-time notifications to your Replit server when data changes — deals move stages, new persons are created, activities are completed, or deals are won or lost. This event-driven model lets you build responsive automations that react instantly to sales activity.

Pipedrive webhooks are configured via the API or in Settings → Tools and integrations → Webhooks → Add webhook. Each webhook has a subscription URL (your deployed Replit endpoint), an event action (added, updated, deleted, merged, all), and an event object (deal, person, organization, activity, all). You can also filter by pipeline or user.

Best practice for webhook setup: create webhooks via API on startup and delete them on shutdown (or use a single global webhook for all events and route by type). Store the webhook ID if you create them via API so you can manage them later.

IMPORTANT: Pipedrive sends webhooks to your subscription URL as JSON POST requests. Your endpoint must return 200 within 15 seconds or Pipedrive marks the delivery as failed. Deploy as Autoscale or Reserved VM — development Repls go offline and cannot receive webhooks reliably.

Pipedrive does not sign webhook payloads by default. Use a secret token in your URL path to prevent unauthorized requests to your endpoint.

```
// pipedrive-webhook-server.js — Handle Pipedrive webhook events
const express = require('express');
const app = express();
app.use(express.json());

// Add a secret to your webhook URL in Pipedrive:
// https://yourapp.replit.app/pipedrive/webhook/YOUR_SECRET
const WEBHOOK_SECRET = process.env.PIPEDRIVE_WEBHOOK_SECRET || 'your-secret-token';

app.post('/pipedrive/webhook/:secret', (req, res) => {
  if (req.params.secret !== WEBHOOK_SECRET) {
    return res.status(403).json({ error: 'Unauthorized' });
  }

  const { event, meta, current, previous } = req.body;
  const { action, object } = meta || {};

  console.log(`Pipedrive webhook: ${action} ${object}`);

  if (object === 'deal') {
    if (action === 'added') {
      console.log('New deal created:', current.id, current.title);
      console.log('Value:', current.value, current.currency);
      // TODO: send Slack notification, create invoice, etc.

    } else if (action === 'updated') {
      // Check if deal was won
      if (current.status === 'won' && previous?.status !== 'won') {
        console.log('Deal WON:', current.title, 'Value:', current.value);
        // TODO: trigger onboarding, notify finance team
      }
      // Check if stage changed
      if (current.stage_id !== previous?.stage_id) {
        console.log(`Deal stage changed: ${previous?.stage_id} → ${current.stage_id}`);
        // TODO: create follow-up activity, send email
      }

    } else if (action === 'deleted') {
      console.log('Deal deleted:', current.id);
    }

  } else if (object === 'person') {
    if (action === 'added') {
      console.log('New person:', current.name, current.email?.[0]?.value);
    }
  } else if (object === 'activity') {
    if (action === 'updated' && current.done) {
      console.log('Activity completed:', current.subject, 'Deal:', current.deal_id);
    }
  }

  // Always respond 200 quickly
  res.status(200).json({ received: true });
});

// Register webhook via API on startup (optional — can also be done manually in Pipedrive UI)
async function registerWebhook() {
  const TOKEN = process.env.PIPEDRIVE_API_TOKEN;
  const baseUrl = process.env.REPLIT_DEPLOYMENT_URL || 'https://yourapp.replit.app';
  const subscriptionUrl = `${baseUrl}/pipedrive/webhook/${WEBHOOK_SECRET}`;

  const response = await fetch(`https://api.pipedrive.com/v1/webhooks?api_token=${TOKEN}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      subscription_url: subscriptionUrl,
      event_action: 'all',
      event_object: 'deal'
    })
  });
  const data = await response.json();
  if (data.success) {
    console.log('Webhook registered, ID:', data.data.id);
  } else {
    console.warn('Webhook registration warning:', data.error);
  }
}

app.listen(3000, '0.0.0.0', () => {
  console.log('Pipedrive webhook server running on port 3000');
  // registerWebhook(); // Uncomment after deploying
});
```

**Expected result:** The webhook server starts on port 3000. After deploying and configuring the webhook URL in Pipedrive Settings, creating or updating a deal triggers the endpoint and logs the event type and deal details to the console.

## Best practices

- Store PIPEDRIVE_API_TOKEN in Replit Secrets (lock icon 🔒) — the token grants the same access as the user who owns it, including the ability to delete all records
- Create a dedicated Pipedrive user account (e.g., api-bot@yourcompany.com) for API token ownership so integration activities are clearly attributed and token rotation does not affect a real user
- Always pass the api_token in the Authorization header as 'Bearer {token}' rather than as a URL query parameter to keep tokens out of server access logs
- Fetch pipeline stage IDs dynamically rather than hardcoding them — stage IDs are account-specific and change if stages are deleted and recreated
- Deploy as Autoscale or Reserved VM for webhook receivers — development Repls go offline when you close your browser, causing Pipedrive to mark webhook deliveries as failed
- Use Pipedrive's 'current' and 'previous' webhook payload fields to implement precise change detection (e.g., only react to stage transitions, not all deal updates)
- Add a secret token to your webhook URL path and verify it on every incoming request to prevent unauthorized access to your webhook endpoint
- Implement retry logic with exponential backoff for Pipedrive API calls — the API has rate limits and occasionally returns 5xx errors that resolve on retry

## Use cases

### Web Form to Pipeline Lead Ingestion

Automatically create Pipedrive persons and deals when web form submissions arrive. Instead of manually entering leads from website contact forms, demo requests, or ad landing pages, a Replit webhook receiver captures the form data, creates a Person with contact details and an associated Deal in the appropriate pipeline stage, and assigns it to the right sales rep based on territory or round-robin logic.

Prompt example:

```
Build a webhook endpoint that receives contact form submissions (name, email, company, message), creates a Pipedrive Person with the contact details, creates a Deal linked to the person in the 'New Lead' stage, adds a note with the form message, and returns the Pipedrive deal ID for confirmation tracking.
```

### Deal Stage Change Automation

Trigger external actions when deals advance through pipeline stages. When a deal moves to 'Proposal Sent', automatically create a follow-up activity due in 3 days. When a deal is marked Won, send a congratulations message to Slack, create a new client record in your accounting system, and schedule an onboarding call. Pipedrive webhooks deliver these stage-change events in real time to your Replit server.

Prompt example:

```
Create a webhook receiver that listens for Pipedrive deal stage changes, sends a Slack notification when a deal is Won including deal value and client name, creates a follow-up 'Onboarding Call' activity in Pipedrive due 7 days later, and logs the event to a database for reporting.
```

### CRM Data Sync and Reporting API

Build a custom reporting layer on top of Pipedrive that aggregates pipeline data for stakeholders who do not need full Pipedrive access. The Replit server queries the Pipedrive API for deals grouped by stage, owner performance metrics, and upcoming activities — then serves a simplified JSON API for a lightweight dashboard or weekly email digest.

Prompt example:

```
Create an API endpoint that queries Pipedrive for all deals in the current month, groups them by stage and owner, calculates total pipeline value and average deal size per stage, and returns a summary JSON suitable for a sales performance dashboard.
```

## Troubleshooting

### Pipedrive API returns { success: false, error: 'Unauthorized' } on all requests

Cause: The PIPEDRIVE_API_TOKEN in Replit Secrets is incorrect or has been rotated. Pipedrive API tokens are 40 characters — a shorter or longer value indicates the wrong field was copied.

Solution: In Pipedrive, navigate to your avatar → Settings → Personal Preferences → API tab. Verify the token matches PIPEDRIVE_API_TOKEN in Replit Secrets exactly (no whitespace). If you have multiple workspaces, ensure you are using the token for the correct workspace.

```
// Quick auth test
fetch(`https://api.pipedrive.com/v1/users/me?api_token=${process.env.PIPEDRIVE_API_TOKEN}`)
  .then(r => r.json())
  .then(d => console.log('Auth test:', d.success ? `OK — ${d.data.name}` : `FAIL — ${d.error}`));
```

### Pipedrive webhooks are not being received — events show as 'failed delivery' in Pipedrive settings

Cause: Your Replit app is in development mode and offline, or the webhook subscription URL does not match your deployed endpoint path including the secret token in the URL.

Solution: Deploy your app using Autoscale or Reserved VM to get a stable HTTPS URL. In Pipedrive Settings → Tools and integrations → Webhooks, verify the subscription URL matches your deployed endpoint exactly. Test the URL is reachable by making a manual POST to it with curl or a tool like Postman.

### Deal is created but stage_id does not correspond to the expected pipeline stage

Cause: Stage IDs are numeric identifiers specific to each Pipedrive account — the same stage name has different IDs across different accounts. Hardcoding a stage ID from documentation or another account will not work.

Solution: Call GET /v1/stages to retrieve all stages with their IDs for your account. Use the stage_name to find the correct stage_id for your pipeline. Store the correct IDs in Replit Secrets or a config file rather than hardcoding them.

```
// Fetch and print all pipeline stages
fetch(`https://api.pipedrive.com/v1/stages?api_token=${process.env.PIPEDRIVE_API_TOKEN}`)
  .then(r => r.json())
  .then(d => d.data.forEach(s => console.log(`Stage ${s.id}: ${s.name} (Pipeline: ${s.pipeline_id})`)));
```

### Custom field data is not saving on deals or persons

Cause: Custom fields in Pipedrive are identified by unique hash keys (e.g., 'a1b2c3d4') rather than human-readable names. Using the field label instead of the hash key silently drops the data.

Solution: Call GET /v1/dealFields or GET /v1/personFields to list all custom fields with their hash keys. Use the hash key (the 'key' property in the response) as the field name in your POST/PATCH requests, not the field label.

```
// List all deal custom fields and their hash keys
fetch(`https://api.pipedrive.com/v1/dealFields?api_token=${process.env.PIPEDRIVE_API_TOKEN}`)
  .then(r => r.json())
  .then(d => d.data.filter(f => f.edit_flag)
    .forEach(f => console.log(`'${f.name}' → key: '${f.key}'`)));
```

## Frequently asked questions

### How do I find my Pipedrive API token?

In Pipedrive, click your avatar or name in the top-right corner → Settings → scroll to 'Personal Preferences' → click the 'API' tab (or look for 'API token' on the page). Your 40-character token is shown there. Copy it to Replit Secrets as PIPEDRIVE_API_TOKEN. If you do not see an API tab, check that your account has API access enabled — some Pipedrive plans restrict API usage.

### Does Pipedrive have a free plan that includes API access?

Pipedrive does not have a free tier, but offers a 14-day free trial with full API access. Paid plans start at the Essential tier, and all paid plans include REST API access. API call limits depend on your plan — higher plans have higher daily API request limits.

### What is the difference between Pipedrive and HubSpot for API integration?

Pipedrive uses simple API token authentication (one secret, no OAuth), making it faster to integrate. HubSpot offers both API key and OAuth options but bundles many more objects and marketing features. Pipedrive's API is more focused on sales pipeline operations (deals, stages, activities), while HubSpot's API covers a much broader surface area including contacts, emails, forms, and marketing workflows.

### What deployment type should I use for a Pipedrive webhook receiver on Replit?

Use Autoscale deployment for most Pipedrive webhook setups — deal and activity events are infrequent enough that Autoscale's occasional 1-3 second cold start is acceptable. Pipedrive marks a delivery as failed if your endpoint does not respond within 15 seconds, so even with cold start you have plenty of time. Use Reserved VM if you need guaranteed sub-second response times for real-time pipeline automation.

### How do I create custom fields on Pipedrive entities via the API?

Custom field creation is done via the management APIs: POST /v1/dealFields to create a new custom field on deals, or POST /v1/personFields for person fields. The response includes the unique hash key for the new field. Use that hash key when reading or writing the custom field value in subsequent API calls — Pipedrive identifies custom fields by hash key, not by label name.

---

Source: https://www.rapidevelopers.com/replit-integration/pipedrive
© RapidDev — https://www.rapidevelopers.com/replit-integration/pipedrive
