Skip to main content

Admin & operations manual — Pipedrive integration + Quote Follow-Up

Audience: administrators & operators (non-developers welcome) · Scope: how to set up, run day-to-day, and troubleshoot the Pipedrive ↔ quotes integration and the Quote Follow-Up landing page — settings, cron jobs, the Pipedrive-side artifacts, and a symptom → fix runbook · Last reviewed: 2026-07-02

TL;DR — what these two features do, in one breath. Every website quote can be mirrored as a Pipedrive deal. The website pushes each quote's content and status to its deal (creating the deal if needed); Pipedrive pushes deal edits back to the quote via a webhook; a cold-quote job nudges reps about stale deals. Separately, the Quote Follow-Up landing page lets a field agent update one quote's status / project stage / expected date / comments from a link in an email — no portal login — and that update flows to the deal too. Everything above runs on cron jobs and a handful of settings; this manual is how you keep it healthy.

This is the operator's view. For the "how it works inside" detail, follow the links:


1. The moving parts you manage

ThingWhere it livesYou touch it when…
SettingsConfigBox backend → Settings (custom settings), collapsible groups Pipedrive Integration, Quote Follow-Up Landing Page, Cold Quotesfirst-time setup, securing the webhook, changing the token lifetime
Cron jobsthe server crontabfirst-time setup / server rebuild; when sync "stops"
Pipedrive account artifactsinside Pipedrive: 3 custom deal fields, 1 webhook (created by the setup CLI) and 1 optional saved filter (backfill accelerator; manual-only since the API v2 migration — the backfill full-scans without it)almost never — do not delete them (see §7)
Setup / maintenance CLIsdocroot/cli/cb_pipedrive_*.phpfirst-time setup, re-securing the webhook, a deliberate backfill
LogsConfigBox/Kenedo log viewer, filtered by log type (see §6)troubleshooting

Per-environment settings. Settings are stored as one row per environment (live, staging, dev) in #__configbox_external_settings. The ConfigBox settings screen edits the row for the environment you're on. Values that come from Pipedrive setup (API token, custom-field keys, filter IDs) are account-wide and the setup CLI writes them to every environment row for you. The only value you set by hand per environment is the webhook auth username/password — set it on the environment you're actually managing (usually live).


2. Pipedrive integration

2.1 What it does

  • Website → Pipedrive (outbound, "deal update"). Any change to a quote — a line item, a price, a quantity, the project name, an agent status, a Follow-Up submission, … — flags the quote for a deal update. A cron job runs every minute, picks up flagged quotes, and pushes one combined payload (title / value / org / person / owner / custom fields and status / lost reason) to the deal, creating the deal the first time. Full trigger + field list: deal-updates.md.
  • Pipedrive → Website (inbound, webhook). When someone edits a deal in Pipedrive, Pipedrive calls our webhook. The endpoint just queues the event; a second cron job drains the queue and applies the change to the matching quote. (Queuing is deliberate — a Pipedrive bulk action fires a burst of webhook calls that would otherwise overwhelm the site.)
  • Cold-quote prompts. A scheduled job emails reps about quotes that have gone cold and writes a Cold Quotes report to a Google Sheet.

2.2 First-time setup (checklist)

Deploying now? For the full ordered go-live runbook (migrations → settings → setup → cron → smoke test, as one checklist), use the deployment playbook. The steps below are the same setup, organised as reference.

Do this once per Pipedrive account (values are account-wide). Requires the Pipedrive API token and the field key of the "Quote number" deal field that already exists in Pipedrive.

  1. Enter the API token. ConfigBox backend → Settings → Pipedrive IntegrationPipedrive API Token. (The setup CLI also writes it, but setting it here first is fine.)
  2. (Recommended) Set the webhook credentials. In the same group, set Pipedrive webhook HTTP auth username and …password to a value unique to this environment (so dev/staging can never authenticate against live). Leaving both blank keeps the endpoint open — see §2.6.
  3. Run the setup CLI. It find-or-creates the Pipedrive fields/webhook, verifies the optional backfill filter, and stores the keys in settings on every environment row:
    PIPEDRIVE_API_TOKEN=<token> php docroot/cli/cb_pipedrive_setup_integration.php \
    --field-key-quote-serial=<the "Quote number" deal-field key> \
    --replace-setup=0 --replace-data=0
    • PIPEDRIVE_API_TOKEN (env var) is required; the CLI stores it into settings.
    • --field-key-quote-serial is required — Pipedrive's opaque hash for your "Quote number" deal field (a real value in .run/Pipedrive - Setup Integration.run.xml).
    • --replace-setup=1 deletes and recreates the fields/webhook (filters are manual-only since the API v2 migration). Use it when you need to re-register the webhook (e.g. after changing the auth credentials — see §2.6). Default 0 leaves anything that already exists untouched.
    • --replace-data=1 runs the destructive backfill (§2.7). Leave 0 for normal setup.
  4. Verify the keys landed (see §6 "verify" queries), then edit or create a test quote and confirm a deal appears/updates in Pipedrive within a minute (with the cron jobs from §2.4 running).

What setup creates in Pipedrive (so you recognise it and don't delete it):

  • Deal fields: Agent Feedback Quote Status (enum: open/won/lost), Agent Feedback Lost Reason (text).
  • Saved filter (optional): "API: Deals with a website quote number" — accelerates the backfill; recreate manually in the Pipedrive UI if lost (condition: the quote-serial custom field IS NOT NULL). The old "API: Cold Quotes" filter has no consumer anymore.
  • A deal webhook (all actions) pointing at https://betacalco.com/pipedrive-quote-updates/updateQuote.

2.3 The follow-up stage pipeline (for Quote Follow-Up stages)

The landing page's project stage picker is the live stages of a Pipedrive pipeline, and picking a stage moves the deal along that pipeline (its native stage_id). Set it up once:

  1. Point at a pipeline. In the CB backend, ConfigBox settings → Pipedrive Integration, set Pipedrive Pipeline ID for follow-up stages (pipedrive_pipeline_id) to the numeric id of the pipeline (find it in Pipedrive, or via the API). It's per-environment, so dev/staging/live can differ.
  2. Import its stages. In the Quote Follow-ups settings group, open the Imported Pipedrive Stages list and click Import pipelines & stages from Pipedrive — one button imports the pipelines then the stages (or run php docroot/cli/cb_pipedrive_import_stages.php). This fills the list with the pipeline's stages (name / order / probability). The imported fields are read-only in the admin (grouped under "Imported from Pipedrive (read-only)") — edit them in Pipedrive.
  3. Curate the UI once. In each stage's "Follow-up settings (editable)" group set the caption (Hint), tick Marks quote won on the stage that means "won", set the Nudge days follow-up cadence (see quote-follow-up/nudge-cadence.md), and untick Visible to agents for any stage you don't want offered. Re-importing preserves these — it only refreshes the Pipedrive-owned fields. A stage removed in Pipedrive is marked inactive, never deleted (a quote may still reference it).

If no pipeline is configured or nothing is imported, the stage picker is empty and the stage is silently not pushed — status, expected date, and the comment note still are. The legacy free-text "Project Phase" field is no longer used (cb_pipedrive_setup_project_phase.php is now a no-op).

2.4 Cron jobs (required)

Two runners must run every minute. Without them, sync silently stops.

* * * * * php /path/to/docroot/cli/cb_pipedrive_update_deals.php # outbound: quotes → deals
* * * * * php /path/to/docroot/cli/cb_pipedrive_process_webhook_queue.php # inbound: drain webhook queue

Plus the cold-quote jobs (not every-minute — schedule to taste; check the server crontab for the live cadence):

# e.g. once a day
0 13 * * * php /path/to/docroot/cli/cb_pipedrive_send_cold_quote_prompts.php # email reps about cold deals
0 13 * * * php /path/to/docroot/cli/cb_pipedrive_update_cold_quote_sheet.php # refresh the Cold Quotes Google Sheet
0 6 * * * php /path/to/docroot/cli/cb_pipedrive_import_pipelines.php # optional: refresh Pipedrive pipelines list
0 6 * * * php /path/to/docroot/cli/cb_pipedrive_import_users.php # optional: refresh the local Pipedrive users list (deal-owner lookup)
0 6 * * * php /path/to/docroot/cli/cb_pipedrive_import_stages.php # optional: refresh follow-up pipeline stages (all pipelines)

Plus the quote-stage-change log mirror — add it only if you configured a Quote stage-change log sheet (Quote Follow-Up settings). The change recording is always-on and synchronous; this job just drains the un-mirrored rows to the sheet, and no-ops when no sheet is set. See quote-follow-up/stage-sync.md.

*/5 * * * * php /path/to/docroot/cli/cb_quote_stage_sync_export_sheet.php # mirror stage-change log → Google Sheet

Plus the follow-up nudge sheet builder — add it only if you configured a Follow-up nudge sheet (Quote Follow-Up settings). It rebuilds/reconciles the sheet of quotes due for a nudge each run (an n8n workflow reads it), and no-ops when no nudge sheet is set. Daily is typical; safe to run hourly. See quote-follow-up/nudge-cadence.md.

15 6 * * * php /path/to/docroot/cli/cb_quote_nudge_build_sheet.php # build "quotes due for a nudge" → Google Sheet

Remove the old outbound runners. cb_pipedrive_export_scheduled.php and cb_pipedrive_sync_quote_data.php were merged into cb_pipedrive_update_deals.php. They still work (they just delegate and log a deprecation notice) but keep only one outbound entry so the queue isn't processed twice at once.

2.5 Day-to-day operation

Normally there is nothing to do — the cron jobs keep quotes and deals in step. Two things you'll actually interact with:

  • The Cold Quotes report sheet. ConfigBox backend → Settings → Cold QuotesCold Quotes Report Sheet selects the Google Sheet the cold-quote job writes to. Reps read that sheet; the email job nudges them.
  • Dry-run the cold-quote emails before a real send:
    php docroot/cli/cb_pipedrive_send_cold_quote_prompts.php --simulate=1
    This sends nothing; it writes an Excel file of who would be emailed to the temp path (the CLI prints the path).

2.6 Securing the inbound webhook (HTTP Basic auth)

The webhook endpoint is fail-open: if both webhook HTTP auth username and …password are blank, it accepts any caller (its original behaviour). To lock it down:

  1. Set both fields (username + password), unique per environment, in the Pipedrive Integration settings group on the environment you're securing.
  2. Re-register the webhook so Pipedrive starts sending those credentials — this needs --replace-setup=1, otherwise setup sees the webhook already exists and won't update it:
    PIPEDRIVE_API_TOKEN=<token> php docroot/cli/cb_pipedrive_setup_integration.php \
    --field-key-quote-serial=<key> --replace-setup=1 --replace-data=0

⚠️ Order matters. If you set the credentials but don't re-register, the site starts rejecting Pipedrive's calls (Pipedrive is still calling with no credentials) and inbound sync stops with 401s. Set the credentials and re-register in the same maintenance window.

2.7 The destructive backfill (redoPipedriveData)

php docroot/cli/cb_pipedrive_redo_quote_data.php (and --replace-data=1 on setup) first NULLs all Pipedrive columns on every quote, then repopulates them from Pipedrive by reading the "deals with a quote number" filter. Run it deliberately — e.g. after a data mishap — never casually. It does not touch the local-only Follow-Up columns (stage / completion date / comments), because Pipedrive can't repopulate those.

2.8 What data moves, and when (quick reference)

This is the operator's cheat-sheet for "which website action pushes what, which quote field becomes which deal field, and what the webhook writes back". Exhaustive, developer-level detail is in deal-updates.md.

A. What triggers an outbound push (website → deal). Any of the actions below flags the quote (pipedrive_update_scheduled = 1); the every-minute runner then sends one combined payload — the payload is the same regardless of which action triggered it (it always re-sends the full picture, content and status). So you never need to reason about "which fields does this action send" — it sends everything.

GroupActions that schedule a push
Commercial / contentadd / edit / copy / delete a line item · change a line-item quantity · toggle a line's "alternative" flag · (re)price a position · Quote Console edits (store position, override price/discount/overage, commission, blank line) · edit quote fields · set/rename the project name · duplicate a quote or create a revision
Status / agent feedbackagent sets status (open/won/lost) · agent sets the lost reason · the out-of-territory (OOT) form is submitted · a Quote Follow-Up landing-page submission
Deletedeleting a quote deletes its deal (rather than pushing an update)

B. Outbound field map (quote → Pipedrive deal). What the combined payload writes:

Pipedrive deal fieldFilled from (the quote)
Titlequote name (or the serial if unnamed) — set on create only, never re-titled
Organizationthe quote's branch organisation
Value / Currencydiscounted quote total / quote currency
Owner + Creatorthe branch RSM's Pipedrive user (fallback pipe@betacalco.com)
Contact personPipedrive person for the quote user's billing email (created if missing)
Quote Serial (custom)the quote serial
Deal Type (custom)derived: name contains "test" → Test; billing email @betacalco.comInternal; else Regular
Deal contains alternatives (custom)derived: any alternative line → Yes, else No
Statuspipedrive_status (sent only when it is open / won / lost)
Lost reasonpipedrive_lost_reason (update only — the create endpoint rejects it)
Agent Feedback Status (custom)pipedrive_agent_feedback_status — the agent's suggestion, incl. won
Agent Feedback Lost Reason (custom)pipedrive_agent_feedback_lost_reason
Is OOT (custom)Yes/No from oot_status == 'approved'
stage_id (native)pipedrive_stage_id (Quote Follow-Up) — moves the deal along the configured pipeline; only sent for a stage in that pipeline
Expected close datepipedrive_agent_feedback_completion_date (Quote Follow-Up) — only when set
Note (not a field)pipedrive_agent_feedback_comments (Quote Follow-Up) — posted once per new/changed comment

Custom fields are only written when their key/name is configured in settings (§6). A blank key silently skips that field; the rest still send.

C. Inbound field map (Pipedrive deal → quote, via the webhook). When a deal is created, updated, or merged in Pipedrive, the webhook (once the queue drains — §2.1) writes these back to the matching quote(s). Quotes are matched by the Quote Serial deal field, then by deal id.

Pipedrive deal fieldUpdates quote column
Statuspipedrive_status
Lost reasonpipedrive_lost_reason
Agent Feedback Status (custom)pipedrive_agent_feedback_status
Agent Feedback Lost Reason (custom)pipedrive_agent_feedback_lost_reason
stage_id (native)pipedrive_stage_id — only when the moved-to stage is an imported stage of the configured pipeline (else skipped)
Expected close datepipedrive_agent_feedback_completion_date
Deal idpipedrive_deal_id (links the quote to the deal when it wasn't linked yet)
Quote Serial (custom)primarily the key used to match the deal to a quote; also written to the quote's serial if it changes on the deal (rare)

The webhook mirrors the editable, quote-owned fields. It writes back the status/feedback columns plus the stage (stage_idpipedrive_stage_id, when the stage belongs to the configured pipeline) and the expected date. It does not write comments back (notes are append-only, website → Pipedrive only). An inbound change never turns around and schedules an outbound push (guarded against a feedback loop).


3. Quote Follow-Up landing page

The follow-up landing page — the no-login page where an agent updates a quote's status / stage / dates / comments from an emailed magic link — is its own system now, and is not re-documented here:

4. Troubleshooting runbook (symptom → check → fix)

"Quotes stopped syncing to Pipedrive" (outbound)

  1. Is the cron running? Confirm cb_pipedrive_update_deals.php is in crontab and firing.
  2. Is a specific quote stuck? A quote that fails 3 times in a row is left flagged but skipped until it changes again. Check the outbound log (custom_pipedrive_exports) for the reason — common ones: the quote's branch has no/invalid RSM email (and the fallback pipe@betacalco.com isn't a Pipedrive user), an API error, or a bad org.
  3. Check the change log (§5) — Diagnostics → Pipedrive Change Log, filter direction Out and type the serial; its sync-health summary also tells you at a glance whether the backlog is queued (cron will get to it), retries exhausted, or stalled (scheduled on a non-latest revision, so the runner will never select it). In SQL: SELECT * FROM …_pipedrive_change_log WHERE serial='…' AND direction='out' shows exactly which deal fields the last push changed (or nothing, if the deal was already in sync).
  4. Un-stick a quote: make any change to it (that reschedules it and resets the retry counter), or fix the underlying cause (RSM email, branch) and re-save.

"Deal edits in Pipedrive aren't showing on the website" (inbound)

  1. Is the queue drainer running? Confirm cb_pipedrive_process_webhook_queue.php is in crontab. Inbound webhooks pile up unprocessed without it.
  2. Is Pipedrive being rejected? If you set webhook credentials but didn't re-register (§2.6), Pipedrive gets 401s. Either blank both credentials (fail-open) or re-register with --replace-setup=1.
  3. Check the queue table #__configbox_external_pipedrive_webhook_queue for rows with status='failed' and read last_error. A failed row retries up to 3 times, then stays failed for inspection.
  4. Check the change log (§5) — Diagnostics → Pipedrive Change Log, filter direction In; the summary's Last inbound change is the quickest "is anything arriving at all?" check. In SQL: SELECT * FROM …_pipedrive_change_log WHERE serial='…' AND direction='in' shows which columns the inbound webhook applied. Nothing there means either the deal field isn't one that syncs back (only the fields in §"reverse direction" do), the label wasn't recognised (Project Phase), or no quote matched the deal.
  5. Reprocess a failed row: fix the cause, then set its status back to pending (it'll be picked up next run), or re-trigger the change from Pipedrive.

"An agent didn't get the follow-up email"

  • The link is emailed only by "Request a new link" today (§3.3) — there's no automatic cadence yet.
  • Sends/failures are logged under the bcquotelandingpage log type.
  • The email always goes to the quote's agent on file — check that the quote's user has a valid email.
  • The token is wrong or expired. Expiry is quote_followup_token_lifetime_days (§3.2).
  • Fix: have them click "Request a new link" on that screen (emails a fresh one), or raise the lifetime setting (takes effect immediately for outstanding links).

"The project stage isn't showing on the deal" (or the picker is empty)

  • No pipeline configured / stages not imported. Set pipedrive_pipeline_id and run the import (backend Import pipelines & stages from Pipedrive button or cb_pipedrive_import_stages.php) — §2.3. The outbound stage_id is only sent for a stage that belongs to the configured pipeline; a stale/foreign id is skipped. Status, expected date, and the comment note are unaffected by this.
  • Stage not published. In the Imported Pipedrive Stages list (Settings → Quote Follow-ups), tick Visible to agents for the stages agents should see.

"A deal was created in the wrong org / on dev it won't write"

  • Outside live, the integration refuses any deal/person write that isn't in the dev/test org (6034) — always on, by design, so dev/staging can never clobber a live deal. On live, any org is allowed. This is expected behaviour, not a bug.

"Comments posted to the deal twice"

  • Shouldn't happen — comments are queued and posted once, then cleared. If you're editing code, don't move comment-posting into the deal-field payload (that payload re-sends every run). See README.md.

5. Change log (always-on field-level audit)

There is nothing to turn on — every field that changes as data moves between a quote and its deal, in either direction, is recorded to the table #__configbox_external_pipedrive_change_log. (This replaced the old opt-in "diagnostic log" Yes/No toggles, which are gone.)

Columns: logged_at (UTC), direction (in = Pipedrive → quote, out = quote → deal), quote_id, serial, deal_id, field (human name), old_value, new_value.

5.1 The backend browser (start here)

Diagnostics → Pipedrive Change Log (controller adminbcpipedrivechanges) reads this table in the admin, so the common questions no longer need SQL. It is read-only by construction — no Add / Remove / Copy task, and every property is a read-only type, so nothing in the UI can write to an audit trail.

  • Filters: logged date, direction, quote serial, deal id, field name. They combine, and the framework remembers them per user between visits. The date filter is a substring match, so type 2026-07-22 for one day or 2026-07 for a month.
  • Sorting: newest first by default; every column is sortable.
  • Long values are truncated in the list (full value in the cell's tooltip); click the row's ID to open the read-only detail view, which shows old_value / new_value in full.
  • A sync-health summary sits above the table — the aggregate view SQL per quote cannot give you: volume in/out over 24h and 7d, distinct quotes touched, how long since the last change in each direction, the outbound backlog (queued / retries exhausted / stalled), linked deals, last successful push, and the inbound queue (pending / failed / oldest pending). Total row count and the oldest row are shown too, alongside the retention in force — see §5.3.
  • "Stalled" is the one worth knowing about: quotes with pipedrive_update_scheduled = '1' that are not the latest revision. getUpdateScheduledQuoteIds() requires is_latest_revision = '1', so the runner will never pick them up — a silent backlog invisible in the per-quote view.

Files (under docroot/components/com_configbox/data/customization/): models/adminbcpipedrivechanges.php, controllers/adminbcpipedrivechanges.php, views/adminbcpipedrivechange(s)/, properties/bcchangevalue.php, assets/css/adminbcpipedrivechanges.css (regenerate the .min.css sibling with csso after editing).

Property types — one new one, deliberately. Every custom property type is a thing somebody has to re-check at a ConfigBox major upgrade, so this screen uses stock and already-existing types wherever they do the job:

ColumnTypeWhy
id, logged_at, serial, quote_id, deal_id, fieldbcimported (already existed)Read-only in the form, normal rendering + search filters in the list. On id it also avoids the stock id type, which hard-codes a row-select checkbox and a check-all — dead controls on a list with no bulk tasks.
directiondropdown (stock)Friendly labels in the list and a dropdown filter, for free.
old_value, new_valuebcchangevalue (new)The one type that had to be written — see below.

bcchangevalue exists for a security reason, not a cosmetic one: KenedoProperty::getCellContentInListingTable() echoes whatever getOutputValueFromRecordData() returns into the listing cell without escaping, and the base implementation returns the raw column value. Verified: both string and bcimported render <img src=x onerror=…> into the page as live markup. These two columns hold values typed by a user in Pipedrive, so the escaping has to happen in the property. It truncates long values in the list too (full value in the tooltip and the detail view).

Two consequences of staying with stock types, both accepted knowingly:

  • The logged_at filter is a substring match, so you filter by typing a date prefix (2026-07-22 for a day, 2026-07 for a month) rather than picking "last 24 hours". With the newest-first default sort that covers the same ground.
  • The stock dropdown filter matches direction with LIKE '%value%', not =. That is only safe because neither in nor out is a substring of the other — re-check this if a third direction token is ever added. The dropdown also renders a <select> in the read-only detail form; it saves nothing (there is no save task), and it should move to a read-only choice type if one lands in properties/.

5.2 What is and isn't recorded

Outbound rows are diffed against the deal's state before the push, so a field appears only when it really changed on the deal. The recorder covers every key the payload can carry:

value · currency · title · status · lost reason · Expected close date · deal owner (user id) · organisation id · person id · quote serial · deal type · has alternatives · Agent Feedback Status · Agent Feedback Lost Reason · OOT · stage · pipeline · deal deletion.

Keep that list in step with the $newDealData assignments in ConfigboxModelBcquotes::updatePipedriveDeal(). A payload key with no matching entry in recordOutboundDealChanges()'s $specs is a field the site changes on the deal with no audit row — the trail then lies by omission. id and creator_user_id are deliberately excluded (plumbing, and create-time only).

Ownership fields need care: Pipedrive returns user_id nested as an object with ->id, and org_id / person_id as objects with ->value, while we send plain ints. normalizeDealValue() unwraps them — without it every push would record a bogus "changed" row for owner, organisation and person.

Inbound rows are written for every quote column the webhook processor changes, including the two derived clears (a non-lost status blanks its lost-reason column) and the first-time deal link (binding a quote to a deal by serial). Those three used to happen silently.

Not recorded, by design: pipedrive_date_last_sync (plumbing), deal fields that don't sync back (nothing changes locally, so there is nothing to audit), and Notes posted to the deal (append-only, and driven by a separate pending column rather than the field payload).

5.3 Retention and the auto-cleaner

The log gets a row per changed field per sync, in both directions, forever — so it is trimmed by a scheduled job.

  • Setting: Custom Settings → Pipedrive Integration → Change log retention (weeks) (default 4). Stored on #__configbox_external_settings_pipedrive (migration 0.5.80), read via BcPipedriveConfig::changeLogRetentionWeeks(). 0 means keep everything forever — a deliberate opt-out; a blank or invalid value falls back to the 4-week default rather than to "unlimited", so nobody ends up with an unbounded table by leaving a field empty.

  • Job: docroot/cli/cb_pipedrive_prune_change_log.php, intended daily:

    30 3 * * * php /path/to/docroot/cli/cb_pipedrive_prune_change_log.php

    Options: --weeks=N overrides the retention for one run, --dry-run=1 reports what would go without deleting. It pushes a Change-Log-Entries-Pruned CloudWatch counter and exits non-zero on failure.

  • How it deletes: BcPipedriveChangeLog::prune() removes rows by logged_at only, in batches of 5 000 (max 200 batches per run), so the first run on a long-neglected table cannot hold a long lock on a table the sync writes to every minute. Anything left over is picked up next run. The cut-off is computed with gmdate() to match how logged_at is stamped.

  • It is destructive — pruned rows are gone. Nothing else in the system deletes from this table.

5.4 By hand, in SQL

To see the recent history of one quote (replace the prefix / serial):

SELECT logged_at, direction, field, old_value, new_value
FROM e5xae_configbox_external_pipedrive_change_log
WHERE serial = 'BC-12345'
ORDER BY logged_at DESC;

Reading it:

  • Outbound rows are diffed against the deal's prior state, so a field appears only when it really changed on the deal — no rows for a push means nothing on the deal changed, not that it didn't run.
  • A newly created deal shows its initial values with old_value empty.
  • Enum fields (Agent Feedback Status) are logged as their labels, not raw option ids.

6. Where to look (logs, tables, verify)

In the backend, firstDiagnostics → Pipedrive Change Log (§5.1). The sync-health summary at the top answers "is it flowing, is anything backed up?" without a query, and the filtered table answers "what happened to this quote/deal?". Drop to the log viewer or SQL below when you need the why rather than the what.

Logs — in the ConfigBox/Kenedo log viewer, filter by these types:

Log typeCovers
custom_pipedrive_exportsoutbound deal updates (the main outbound log)
custom_pipedrive_sync_quote_datainbound webhook ingress + queue processing
custom_pipedrive_cold_quote_promptscold-quote email job
custom_pipedrive_setupsetup CLI (fields/filters/webhook creation)
custom_pipedrive_redo_quote_datathe destructive backfill
bcquotelandingpageQuote Follow-Up landing page (sends, save failures)

Database tables (local prefix e5xae_; live has its own prefix):

TableWhat to check
#__configbox_external_user_quotesper-quote state: pipedrive_update_scheduled (1 = queued outbound), pipedrive_update_attempts (retry counter, capped at 3), pipedrive_deal_id (the linked deal), pipedrive_date_last_export (last successful push), pipedrive_stage_id (chosen follow-up stage), pipedrive_agent_feedback_* (Follow-Up fields)
#__configbox_external_pipedrive_stagesthe imported follow-up pipeline stages: pipedrive_stage_id, name, order_nr, published, is_won_stage, active_flag (§2.3)
#__configbox_external_pipedrive_webhook_queueinbound queue: status (pending/failed), attempts, last_error
#__configbox_external_pipedrive_change_logfield-level audit of every in/out change (direction, field, old_value, new_value, UTC logged_at) — see §5
#__configbox_external_settingsone row per environment; the general settings
#__configbox_external_settings_pipedriveone row per environment (settings_idexternal_settings.id); all the Pipedrive settings (token, field keys, filter IDs, webhook auth, OOT options). Split off the wide settings table by migration 0.5.56; the settings screen joins it in transparently

Verify setup landed (local DB bc_master; adjust for live):

mysql -uroot -p bc_master -e "SELECT s.environment, \
LEFT(p.pipedrive_api_token,6) AS token_prefix, \
p.pipedrive_field_key_quote_serial, p.pipedrive_pipeline_id, \
p.pipedrive_filter_id_deals_with_quote_serial, \
p.pipedrive_webhook_auth_user <> '' AS webhook_secured \
FROM e5xae_configbox_external_settings s \
JOIN e5xae_configbox_external_settings_pipedrive p ON p.settings_id = s.id;"

7. Safety notes — the "don'ts"

  • Don't delete the Pipedrive artifacts setup created — the 2 custom deal fields, the 2 filters, or the webhook. The integration reads their keys from settings; deleting them breaks sync until you re-run setup.
  • Don't rename the configured pipeline's stages expecting the picker to follow automatically. The picker keys on the numeric stage id (rename-safe), but re-run the import to pull new names/order and to pick up added/removed stages.
  • Don't leave the webhook credentials set but un-registered. Either blank both (fail-open) or set them and re-register with --replace-setup=1 (§2.6). The half-configured state rejects live webhooks.
  • Don't run the backfill (redoPipedriveData / --replace-data=1) casually — it NULLs every quote's Pipedrive columns first (§2.7).
  • Don't run two outbound runners on cron at once. Keep only cb_pipedrive_update_deals.php.
  • Don't set PIPEDRIVE_TEST=capture on the live server — it silently stops real syncing (it's a testing-only mode; see testing.md).
  • Use different webhook credentials per environment, so dev/staging credentials can never authenticate against live.
  • The change log is pruned on a schedule, not never — see §5.3. Retention is a setting; the browser's summary (§5.1) shows the total row count and oldest row so you can see the effect. Its own queries stay bounded by logged_at, so the page doesn't get slower as the table grows.

  • README.md — architecture, the four flows, the foundational layer, deploy steps
  • deal-updates.md — every outbound trigger and the exact deal fields written
  • quote-follow-up/landing-page.md — the landing page: states, authentication, tokens, data model
  • testing.md — safely testing the deal-update flow (capture mode + the org-6034 guard)
  • migrations.md — how the schema changes above are applied and tracked