Skip to content

CRM & Integrations

Pipedrive API Integration: A Developer's Guide to JSON Payloads, Webhooks, and Sync Patterns That Survive Production

Pipedrive's API Is Powerful but Full of Surprises

📈

Pipedrive is our primary CRM. Every lead, every deal, every customer interaction flows through it. And over the past two years, we have built deep integrations between Pipedrive and our internal tools — lead routing, automated deal creation, field rep assignment, and reporting dashboards that pull real-time data from the Pipedrive API.

The API itself is capable, but the documentation has gaps, and there are patterns you only discover after things break in production. This guide is the JSON-level reality of that work — the payloads, the gotchas, and the architecture that has survived every format change so far.

How to Authenticate With the Pipedrive API

Pipedrive offers two authentication methods: API tokens for simple integrations, and OAuth2 for marketplace apps that access other companies' Pipedrive accounts. For internal integrations where you are accessing your own Pipedrive data, API tokens are simpler and work fine. The token goes in a query parameter or header on every request:

curl 'https://api.pipedrive.com/v1/deals?limit=100&api_token=YOUR_TOKEN'

For anything production-facing, we store the API token in Supabase Vault and access it only from server-side functions. Never expose a Pipedrive API token to the browser — it grants full access to your CRM data.

Reading Pipedrive's JSON: Custom Field Hash Keys

Pipedrive supports custom fields on every entity — deals, persons, organisations, activities. This is incredibly useful for mapping your business data into the CRM. But the JSON that comes back has a trap: custom fields appear under randomly generated hash keys instead of human-readable names, with no indication of what they represent.

{
  "success": true,
  "data": {
    "id": 42,
    "title": "Website lead — Smith",
    "value": 5000,
    "currency": "AUD",
    "abc123_custom_field": "4 Example St, Southport QLD"
  }
}

Which field is abc123_custom_field? Nothing in the payload will tell you. Our approach is a mapping layer: we fetch the field definitions from the /dealFields endpoint on deployment and cache them, so application code references readable names like property_address while the API layer translates to the hash key automatically. When someone adds or renames a custom field in the Pipedrive UI, we re-fetch the definitions. Without this layer, custom field code is unreadable and breaks silently when fields are renamed.

Surviving the Rate Limits

Pipedrive rate limits are tighter than you might expect. The standard plan allows 100 requests per 10-second window. That sounds reasonable until you need to sync thousands of deals or update hundreds of contacts. We hit rate limits during our first bulk sync and had to rewrite the entire process with request queuing and backoff logic.

Our sync pattern now batches requests in groups of 80 — leaving headroom below the 100-request limit — with a 10-second pause between batches. For bulk operations, use the batch-friendly endpoints where available: /deals supports fetching up to 500 deals per request with pagination, which beats fetching one at a time by a comfortable margin.

Handling Pipedrive Webhooks

Pipedrive webhooks work differently from Stripe or GitHub webhooks. The JSON payload structure varies significantly by event type. Deal update webhooks include the full deal object. Person update webhooks include a previous and current state. Activity webhooks sometimes include related entities and sometimes do not.

We built our webhook handler with explicit parsing for each event type rather than trying to write a generic handler. This is more code, but it is reliable — a generic parser works right up until the one event shape you did not anticipate arrives at 2am and vanishes into a catch block. The bigger gotcha is that Pipedrive webhook payloads can include sensitive data — phone numbers, email addresses, deal values. Make sure your webhook endpoint is secured and your logging does not store personally identifiable information in plain text.

The Sync Table Pattern

We do not write Pipedrive data directly into our application tables. We maintain a sync layer — dedicated tables that mirror Pipedrive's data structure. A pipedrive_deals table, a pipedrive_persons table, and a pipedrive_activities table. Each has a pipedrive_id column, a synced_at timestamp, and columns that match Pipedrive's response fields. A separate process maps this synced data into our application's domain models.

When Pipedrive changes their API response format — which has happened twice in the past year — we update the sync layer without touching our core business logic. This separation has saved us hours of debugging.

How to Create Deals From a Web Form

Our web forms create Pipedrive deals automatically. When a potential customer submits a lead form, a Supabase Edge Function creates a person, creates a deal associated with that person, adds a follow-up activity, and populates custom fields with the form data — synchronously, so the user sees a confirmation page within two seconds.

// inside a Supabase Edge Function — pd() wraps fetch with the base URL + token
const person = await pd('POST', '/persons', {
  name: form.name,
  email: [{ value: form.email, primary: true }],
});

const deal = await pd('POST', '/deals', {
  title: form.name + ' — website lead',
  person_id: person.id,
  [FIELD_MAP.property_address]: form.address, // hash key via the mapping layer
});

await pd('POST', '/activities', {
  subject: 'Call new website lead',
  type: 'call',
  deal_id: deal.id,
});

The key is error handling. If the person creation succeeds but the deal creation fails, you need to handle partial success gracefully. We wrap the entire flow in a transaction-like pattern — if any step fails, we log the error and the partial state so it can be completed manually. Half a lead in the CRM is recoverable; a silently dropped lead is not.

Real-World CRM Integration Architecture

Put the pieces together and the shape of a production integration looks like this. Inbound, web forms hit an Edge Function that writes to Pipedrive through the field-mapping layer. Outbound, Pipedrive webhooks land in a secured endpoint that updates the sync tables, with a scheduled sync sweeping up anything webhooks missed. On top of the sync tables, materialised views aggregate the data, and dashboards subscribe to changes — when a rep moves a deal to a new stage in Pipedrive, the dashboard updates within seconds.

Our most-used internal tool is exactly that: a real-time dashboard visualising deal stages, monetary values, expected close dates, and assigned reps. Building it required understanding Pipedrive's structure — deals belong to pipelines, pipelines have stages, and each stage has an order_nr that determines its position. We query the /pipelines endpoint for stage definitions and sort the board by stage order.

This is also the architecture we productised. Our web development builds on Velocity X ship with CRM integration wired in from day one — whether the client runs HubSpot, Pipedrive, or Attio, the same pattern applies: forms in, webhooks out, a sync layer in between, and dashboards on top.

What We Wish We Had Known

Pipedrive's API documentation is adequate but not comprehensive. Some endpoints have undocumented query parameters that you only find in community forums. The search endpoint is surprisingly powerful but slow on large datasets — we cache search results aggressively. Activity types are fixed and cannot be customised via the API.

And the most important lesson: Pipedrive's API is not a real-time data source. Build your integrations around webhooks for real-time updates and scheduled syncs for catching anything webhooks missed. Do that, keep the field mapping and sync layers between Pipedrive and your business logic, and the integration becomes the boring, reliable kind — which is the only kind worth having in front of your pipeline.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.