Webhooks & the local dev loop
Pipedrive can POST an event to a URL every time a record changes. pdcli gives you two
things: plain CRUD over your account’s webhooks, and webhook listen, a local dev loop
that stands up a receiver, registers a throwaway webhook pointing at it, and prints and
forwards each delivery, the way stripe listen does.
Webhooks are a v1 feature. pdcli talks to /api/v1/webhooks, and v1 supports
create and delete only — there is no update. To change a subscription, delete it and
create a new one.
Managing webhooks
Section titled “Managing webhooks”webhook list shows every webhook on the account, with its subscription URL, the
action/object pair it fires on, and whether it is active:
pdcli webhook listpdcli webhook list --output jsonwebhook create registers one. A webhook subscribes to exactly one action on one
object, each of which may be a * wildcard. --url is where Pipedrive POSTs, and
must be a public https endpoint:
pdcli webhook create --url https://example.com/hook --event-action change --event-object dealpdcli webhook create --url https://example.com/hook --event-action "*" --event-object "*"--event-actionis one ofcreate,change,delete, or*.--event-objectis one ofactivity,deal,person,organization,product,lead,note,pipeline,stage,user, and a handful more (--helplists them all), or*.--versiondefaults to2.0(the v2 payload shape).--namelabels the webhook.--http-auth-userand--http-auth-passwordprotect your endpoint with basic auth; the two go together, or pdcli rejects the pair.
webhook delete <id> removes one. It confirms first (skip with -y/--yes); declining
aborts with exit 1:
pdcli webhook delete 3pdcli webhook delete 3 --yesThe local dev loop: webhook listen
Section titled “The local dev loop: webhook listen”webhook listen is the fast path for building against webhooks without deploying anything.
In tunnel mode it:
- Binds a local receiver on
127.0.0.1:<port>(default3000). - Registers a temporary catch-all webhook (named
pdcli-listen:<uuid>) pointing at your public tunnel URL, with generated basic-auth credentials. - Pretty-prints every delivery, and optionally forwards the raw payload to your app.
- Deletes the temporary webhook when you stop it.
# tunnel already running: ngrok http 3000 → https://abc123.ngrok.apppdcli webhook listen --url https://abc123.ngrok.app --forward-to http://localhost:3000pdcli does not bundle a relay. You provide the public tunnel (ngrok, cloudflared, or
anything that terminates https and forwards to your local port), and pass its URL as
--url. Pipedrive POSTs to the tunnel, the tunnel forwards to the pdcli receiver on
--port, and pdcli handles the delivery.
Each delivery prints one line in a terminal — a timestamp, the entity.action key, and the
record id. Piped or under --output json, every delivery is emitted as a JSON line
(NDJSON), one object per delivery, so you can pipe it straight into jq or a file. Add
--forward-to <url> to also POST the raw payload to your local app, exactly as it arrived.
Filtering with --events
Section titled “Filtering with --events”The temporary webhook is a catch-all, and pdcli filters client-side. --events takes a
comma-separated list of entity.action patterns; * is a wildcard on either side, and a
bare entity means every action on it:
pdcli webhook listen --url https://abc123.ngrok.app --events deal.change,person.*deal.change,person.* prints deal changes and every person event. Omit --events to see
everything.
Stopping and cleanup
Section titled “Stopping and cleanup”Stop with Ctrl-C (SIGINT), or bound the run with --once (stop after the first delivery)
or --max-events <n>. However it stops, pdcli deletes the temporary webhook so nothing is
left pointing at a tunnel that is about to disappear.
If a previous run crashed before it could clean up, its orphaned pdcli-listen: webhook is
swept on the next start. The run id is unique per session and only stale, untracked
leftovers are removed, so two webhook listen sessions can run at once without deleting
each other’s live webhook.
Why forged deliveries can’t slip through
Section titled “Why forged deliveries can’t slip through”A public tunnel URL is guessable, so the receiver does not trust every POST that reaches it. The temporary webhook is registered with generated basic-auth credentials, and the receiver rejects with 401 any POST that does not carry them. Only Pipedrive was handed those credentials, so a forged request to your tunnel is never printed and never forwarded.
--synthetic: the change feed as webhook deliveries
Section titled “--synthetic: the change feed as webhook deliveries”Sometimes you cannot receive an inbound webhook at all — you are behind a firewall, on a
laptop with no tunnel, or writing a reactive agent that only makes outbound calls.
--synthetic covers that case. Instead of registering a real webhook, it polls the
incremental change feed and emits the same delivery envelope a webhook would, with zero
inbound network:
pdcli webhook listen --synthetic --since 15mpdcli webhook listen --synthetic --since 7d --events deal.change --forward-to http://localhost:3000Each cycle fetches everything changed across deals, persons, organizations, activities, and
products since the last watermark, and emits one event per record. The event envelope is
{ event, meta, current, previous } (previous is always null — the feed carries only
current state). --events, --forward-to, and the JSON/NDJSON output all behave exactly
as they do in tunnel mode, so code written against synthetic deliveries also handles real
ones.
--synthetic keeps its own watermark, separate from the changes
command’s. The first run needs a --since <timestamp|Nd> to anchor from (a bare first run
with no stored watermark exits 64); after that it resumes on its own. --interval sets the
gap between poll cycles (default 10s), and --once runs a single cycle then exits.