# Pipedrive integration

> Create TimeTracking clients and placements from won Pipedrive deals using Pipedrive webhooks and idempotent external-ID upserts.

URL: https://timetracking.tech/developers/integrations/ats-crm/pipedrive
Last updated: 2026-10-24

## 1. Summary

**Status:** Recipe (no native connector).

**Direction:** Pipedrive to TimeTracking (clients, placements). Optional: approved totals back to a deal field.

## 2. Mechanism

- **Pipedrive side.** Webhook `updated.deal` (v1) or `change.deal` (v2); filter on status changed to `won`. Read organization and person via `/v1/organizations/{id}`, `/v1/persons/{id}`. Custom fields are keyed by hash ids.
- **Glue.** Receiver or iPaaS ([Zapier](https://timetracking.tech/developers/integrations/ipaas/zapier), [Make](https://timetracking.tech/developers/integrations/ipaas/make), [n8n](https://timetracking.tech/developers/integrations/ipaas/n8n)).
- **TimeTracking side.** `PUT /clients/by-external/pipedrive/{orgId}`, `PUT /people/by-external/pipedrive/{personId}`, `PUT /placements/by-external/pipedrive/{dealId}`.
- **Totals back (optional).** `GET /days?from&to&placement_id=…&status=approved`.

Assumption — verify with your Pipedrive admin: which custom fields hold start date, end date, worker and employee number.

## 3. Prerequisites

- Pipedrive API token and admin rights for webhooks.
- Custom deal fields: start date, end date, job title.
- TimeTracking API key (`write`).

## 4. Entity mapping

| Pipedrive object.field | TimeTracking field | Notes |
|---|---|---|
| Organization `id` | client external ref | |
| Organization `name` | client `name` | |
| Person (worker) `id` | person external ref | Only if workers are Pipedrive persons. |
| `first_name`, `last_name`, `email[0].value`, `phone[0].value` | person fields | |
| Person custom "Employee number" | `employeeNumber` | |
| Deal `id` | placement external ref; `ref` `PD-<id>` | |
| Deal custom start / end | `startsOn` / `endsOn` | |
| Deal `title` / custom job title | `roleTitle` | |
| Deal lost/closed after start | `DELETE /placements/{id}` (deactivate) | |

**External-ID strategy.** `source` = `pipedrive`. No ids need to be written back to Pipedrive: the deal id is the key. Upserts are full replaces.

## 5. Step-by-step setup

1. Add the Pipedrive webhook (`updated.deal`) to your receiver; protect it with HTTP basic auth.
2. Ignore events unless `current.status == "won"` and `previous.status != "won"` (or the date fields changed on a won deal).
3. Upsert:

```sh
curl -s -X PUT "$TT/clients/by-external/pipedrive/311" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"name":"Logistiek Parkstad","externalRef":"311"}'
curl -s -X PUT "$TT/people/by-external/pipedrive/9001" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"employeeNumber":"1002","firstName":"Fatima","lastName":"El Amrani","email":"fatima@example.com"}'
curl -s -X PUT "$TT/placements/by-external/pipedrive/4410" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -H "Idempotency-Key: pd-deal-4410-won" \
  -d '{"personExternalRef":"9001","clientExternalRef":"311","ref":"PD-4410","roleTitle":"Orderpicker","startsOn":"2026-11-02","endsOn":"2027-01-29"}'
```

## 6. Sync schedule and triggering events

| Trigger | Action |
|---|---|
| `updated.deal` to won | Upsert client, person, placement. |
| Date fields changed on a won deal | Upsert placement again (full replace). |
| `timesheet.approved` (optional) | `GET /days?placement_id=…&status=approved&from&to`, write the sum to a deal field. |

## 7. Corrections, conflicts and locked periods

TimeTracking owns hours, conflicts and locks. Recompute totals from `GET /days` (never increment). Ending a placement before existing hours returns `409`.

## 8. Testing checklist

- [ ] Winning a test deal returns `created: true`; re-saving returns `created: false`.
- [ ] Changing the end date updates `endsOn`.
- [ ] Losing or reopening a deal creates nothing.

## 9. Troubleshooting

| Error | Fix |
|---|---|
| `422` "Person and client must exist …" | Upsert person and client first (same source). |
| `422` on dates | Empty custom date field; skip or default. |
| `409` | Placement shortened before existing hours. |
| `429` | Wait `Retry-After`. |

## 10. Limits and known gaps

No client-contact API. Native Pipedrive marketplace app (OAuth2): Planned.

## 11. Build it with Claude

```text
Build a Make (or n8n) scenario: Pipedrive webhook updated.deal → filter status changed to won → get organization
and person → TimeTracking PUT /clients/by-external/pipedrive/{orgId} {name} →
PUT /people/by-external/pipedrive/{personId} {firstName, lastName, email, employeeNumber} →
PUT /placements/by-external/pipedrive/{dealId} {personExternalRef, clientExternalRef, ref "PD-<id>", roleTitle,
startsOn, endsOn}. Base https://app.timetracking.tech/api/v1, Bearer key, responses { data } / { error }.
Ask me for my custom field hashes. Error route for 409/422/429.
```
