Pipedrive CRM Sync
Pushes Volāre records (leads, clients, bookings, notes, passengers, products) to Pipedrive CRM via queued jobs, with a polymorphic sync tracking system and a Filament monitor page.
When to Use
Section titled “When to Use”- Keep the sales team’s Pipedrive account in sync with Volāre data automatically
- Monitor and retry failed syncs from the admin panel
- Reconcile local sync state against the live Pipedrive account
- Provision or heal the Pipedrive pipeline, stages, and custom fields
What Syncs
Section titled “What Syncs”Mapping defined in PipedriveSyncRegistry::MODELS:
| Volāre model | Pipedrive entity | Job |
|---|---|---|
Lead |
Lead (/leads) |
SyncLeadToPipedriveJob |
Client |
Person (/persons) |
SyncClientToPipedriveJob |
Passenger |
Person (/persons) |
SyncPassengerToPipedriveJob |
Booking |
Deal (/deals) |
SyncBookingToPipedriveJob |
BookingUpsell |
Product (/products) |
SyncProductToPipedriveJob |
ProductByMarket |
Product (/products) |
SyncProductByMarketToPipedriveJob |
BookingNote |
Note on a deal (/notes) |
SyncBookingNoteToPipedriveJob |
BookingNote is intentionally outside the registry: notes have no standalone endpoint for reconciliation, so they are dispatched directly by the observer.
AttachProductsToDealJob links products to a deal’s Products tab: the main trip (the booking’s ProductByMarket, quantity 1 at the booking base price) plus each upsell. It deduplicates against products already attached, is unique per booking, and is (re-)dispatched by the booking and product sync jobs.
Architecture
Section titled “Architecture”Model saved (created/updated) -> PipedriveSyncObserver dispatches sync job (skipped if no API token) -> Job on the `pipedrive-sync` queue (tries: 3, backoff: 30/60/180s) -> AbstractPipedriveSyncJob.handle() -> PipedriveSync row: Pending -> Syncing -> Synced/Failed/Ignored -> PipedriveClient: POST to create, PUT to `{endpoint}/{pipedrive_id}` to updateKey components:
PipedriveSyncObserver— Registered onClient,Booking,Passenger,BookingUpsell,BookingNote,LeadinAppServiceProvider. Fires oncreatedandupdated. No-ops entirely whenpipedrive.api_tokenis empty.HasPipedriveSynctrait — Gives models thepipedriveSync()morph relation,getOrCreatePipedriveSync(), and a deterministicpipedriveExternalId()(volare_<table>_<id>) for idempotency.PipedriveSyncmodel — Tracking row per synced model:pipedrive_id,external_id,status,attempts,error, last sentpayload. Statuses: Pending, Syncing, Synced, Failed, Skipped, Ignored (seePipedriveSyncStatus).PipedriveClient— HTTP client with timeout/retry, typed exceptions (auth, rate limit, validation, timeout), and credential-sanitized logging.PipedriveAuthService— Static API token auth via thex-api-tokenheader (no OAuth flow).PipedriveSettingmodel — DB key-value store (pipedrive_settingstable) for pipeline ID, stage IDs, and custom-field hash keys, cached forever and cleared on write. Populated by the setup commands, never hardcoded.BuildsPipedriveCustomFields/LoadsPipedriveCustomFieldstraits — Payload builders translate logical field names into storedfield_<name>hashes (blank values are skipped so an unresolved field never wipes a CRM value); commands load live account fields to detect orphaned keys.
Source: backend/app/Services/Pipedrive/, backend/app/Jobs/Pipedrive/
Business Rules
Section titled “Business Rules”- One-way sync (Volāre → Pipedrive). Volāre is the source of truth; nothing is read back into Volāre models.
- Records deleted by hand in Pipedrive stay deleted. An update against a deleted entity marks the sync row
Ignored— final and non-retryable — instead of re-creating it. Use the monitor’s Reactivate action after restoring the record from Pipedrive’s recycle bin. - Notes only sync outward. Only
BookingNoterows with sourceVolareare synced, preventing loops with notes that originated in Pipedrive. - Rate limits release, they don’t fail. A 429 resets the row to Pending and releases the job for the
Retry-Afterdelay. Non-retryable errors (e.g. orphaned custom-field hashes) are reported to Sentry/Nightwatch and fail the job. - Leads are create-only.
pipedrive:sync-all --forcere-syncs already-synced records but skips leads.
Configuration
Section titled “Configuration”Required env vars: PIPEDRIVE_API_TOKEN (from Pipedrive → Settings → Personal preferences → API), PIPEDRIVE_API_URL
Optional env var: PIPEDRIVE_QUEUE (defaults to pipedrive-sync)
Config file: backend/config/pipedrive.php (timeout, retry, logging)
Pipeline/stage IDs and custom-field keys live in the pipedrive_settings table, not in config.
Admin Monitor
Section titled “Admin Monitor”PipedriveSyncMonitor (System → Pipedrive Sync, admin-only) lists all PipedriveSync rows with status/model filters, a stats widget, and 30s polling. Row actions:
- Retry (Failed rows) — resets to Pending; re-processed by
pipedrive:sync-allor the next model update. - Reactivate (Ignored rows) — resets to Pending after the record has been restored in Pipedrive.
Source: backend/app/Filament/Pages/PipedriveSyncMonitor.php
Artisan Commands
Section titled “Artisan Commands”| Command | Purpose |
|---|---|
pipedrive:setup-pipeline |
Create the sales pipeline and its 8 stages |
pipedrive:setup-custom-fields |
Create required deal/person custom fields |
pipedrive:sync-all |
Dispatch jobs for unsynced records (--model, --limit=500, --force) |
pipedrive:retry-failed |
Re-dispatch failed/pending sync rows (--status, --model, --limit=100) |
pipedrive:reconcile |
Compare local sync rows against Pipedrive, report drift (--fix resets broken rows to pending) |
pipedrive:reconcile-custom-fields |
Re-map stored field keys to live account fields by name, healing orphans (--dry-run) |
pipedrive:ignore-deleted |
Park rows stuck retrying because their Pipedrive entity was deleted (--dry-run) |
pipedrive:recover-overflowed-syncs |
Reset rows that crashed on the attempts smallint overflow so they re-sync (--dry-run) |
pipedrive:cleanup-legacy-stages |
Delete legacy CheckoutStep-based stages and their settings rows |
Source: backend/app/Console/Commands/Pipedrive*.php
Related
Section titled “Related”- Queue System — the
pipedrive-syncqueue must have a worker - Source:
backend/app/Services/Pipedrive/PipedriveClient.php - Source:
backend/app/Jobs/Pipedrive/AbstractPipedriveSyncJob.php - Source:
backend/app/Observers/PipedriveSyncObserver.php