New Lead marketplace workflow action (FM-2606)
Internal runbook. Operator-facing steps live indocs/operators/getting-started/new-lead-workflow-action.mdx.
What it is
The marketplace app’s existing draft workflow actioncreate_fm_applicant
(“Create Funding Machine Applicant”, templateId 01KM6NZ3ERBWMSCMT231E6VTWG) is
repointed so its execution URL is POST /api/marketplace/new-lead. That draft
pre-dated FM-2606 with a custom payload mirroring the webhook body and no execution
URL; it was never published. Reusing it avoids a second near-duplicate action. It is the drop-in
replacement for the workflow “Custom Webhook” step that posts to
POST /api/webhooks/new-lead.
Both routes are thin adapters over the ONE intake + applicant-gate + auto-credit-pull
implementation in lib/webhooks/new-lead-intake.ts (processNewLeadIntake). They
differ only in transport:
Execution payload the CRM sends:
{ "success": true, "leadEventId": "...", "duplicate": false }.
leadEventId and duplicate are exposed to later workflow steps as custom variables.
Why dual execution cannot double pull or double charge
Idempotency is not in either adapter; it is in the shared implementation and the Convex mutations it calls, so it holds regardless of which ingress fires:leadEvents.logLeadEventcollapses a second delivery inside its dedupe window, or while an applicant-opportunity claim is unresolved, into the same lead event.leadEvents.claimLeadEventOpportunitylets exactly one caller create the applicant opportunity for a lead event.autoCreditPullEvents.reserveTriggerlets exactly one caller reserve the auto pull per(locationId, contactId), and any prior decision short-circuits before the wallet or bureau is touched. Outside the lead-event window a second ingress still gets its own lead event and application (one application per submission is the existing policy; the CRM gives the action no submission id to key on, and inventing one would change the webhook’s semantics too) but it cannot pull. Operator docs therefore say: back to back is fine, never separate the two steps with a Wait./api/credit-report/newkeeps its own wallet/charge checks unchanged.
tests/newLeadApplicantPerSubmission.test.ts (“FM-2606” block) drives the webhook
route and the action route against the same in-memory Convex state and asserts one
application and one bureau POST in both the inside-window and outside-window cases.
Where it is in the developer portal
Workflow actions are app-scoped and are not in the app version dropdown (v7.0.1 / v6.0.0). In the developer portal open App Settings for the app, thenmodules/workflows → Actions. Direct link to the draft config (Brock, 2026-09-17):
https://marketplace.gohighlevel.com/app-settings/697a568ce94ccaee772a84b0/version/6a74dfe1a56f7909daa13f72/modules/workflows/actions/69bdcaa344f5e92d32354e65/config?version=1.0&tempId=01KM6NZ3ERBWMSCMT231E6VTWG
The action is “Create Funding Machine Applicant” (id 01KM6NZ3ERBWMSCMT231E6VTWG). The
portal’s “Test” button and ghl app actions test both execute the real endpoint; do
not use them against production until GHL_NEW_LEAD_ACTION_TOKEN is set and a test
sub-account and contact have been agreed, because a successful run writes a lead event
and can trigger a billable pull.
Environment
GHL_NEW_LEAD_ACTION_TOKEN: static bearer the action sends. Generated withopenssl rand -hex 32. Set on Vercel (projectghl-marketplace-app-fundingmachine, team My Figures) on Production, Preview, and Development as a Sensitive variable on 2026-09-17 with Brock’s approval; the portal draft header carries the same value. Existing deployments do not see a new variable until they redeploy; merging this PR deploys production and picks it up.- The proxy exempts the exact path
/api/marketplace/new-lead(seelib/proxy/isExemptPath.ts); the route verifies the bearer itself.
Marketplace configuration with the official CLI
Use the official@gohighlevel/marketplace-cli in isolation (not ~/.local/bin/ghl).
Run everything from a scratch directory outside the repo and pin the version.
- Only the
ghl app actions ...sub-commands. Neverghl app push: it owns the app profile, OAuth, and the shared marketplace webhook (src/webhooks/ghl-webhooks.json), none of which this task touches. ghl app actions publishis HELD. Publishing makes the action visible to every installed sub-account and needs its own approval. Publish only after this PR has merged and deployed, otherwise executions hit a production proxy that has no route.- If
workflows.readonlyis missing from the app’s scopes, stop. Adding a scope is Sensitive Scope (auth/scopes) and forces a major version bump on the next app publish; it needs separate approval. - The header value
${env:GHL_NEW_LEAD_ACTION_AUTHORIZATION}is resolved at push time from the shell and never written to disk or to git. After a pull the portal value is exported as${remote}.
Status from the 2026-09-17 run
- Logged in as the Funding Machine developer account (isolated
GHL_CONFIG_DIR). - App identified: “My Funding Machine”, appId
697a568ce94ccaee772a84b0, live version7.0.1(versionId6a74dfe1a56f7909daa13f72).workflows.readonlyis already inoauth.allowedScopes; no scope change needed. - Existing remote actions before the change:
create_fm_applicant(draft 1.0, custom payload mirroring the webhook body, no execution URL, created 2026-03-20) andfm_admin_processing_form_submitted(draft 1.0, untouched). ghl app actions pushapplied exactly one operation,update:create_fm_applicant@1.0, and the post-pushghl app actions diffis empty. The action is still a DRAFT (isActive: false); nothing is visible to sub-accounts untilpublish, which is held.- The previous draft definition is preserved at
/Users/fm-mini/tmp/cos-launch/fm-2606-ghl-cli/create-fm-applicant.PREVIOUS-DRAFT.json. - The bearer was generated with
openssl rand -hex 32into the 0600 file/Users/fm-mini/tmp/cos-launch/fm-2606-ghl-cli/secrets/GHL_NEW_LEAD_ACTION_TOKENand injected at push time, then set asGHL_NEW_LEAD_ACTION_TOKENon Vercel (see Environment above). It was never printed or committed. ghl app pullfor the full app stops at billing validation because the portal’s existing usage meters fail the CLI’s local rules (pricingPageUrl,minPricePerUnit). App, webhook, and action files still pull. Not this task’s problem; do not “fix” the meters from here.- Not run:
ghl app push(app profile / shared webhook untouched),ghl app actions publish,ghl app actions test(would execute a real intake against production).
CLI verification on 2026-10-06
- The pinned official CLI
1.0.0successfully pulled the full app with--with-typesafter the billing configuration repair documented inmarketplace-meters.md. The September 17 billing-validation blocker is resolved. create_fm_applicant@1.0remains a draft. Local action validation, including--publishable --action create_fm_applicant --version 1.0, passes. Its pulled definition matches the repository definition apart from the generated$schemareference and the redacted${remote}authorization placeholder.- Schema and declaration generation succeeds. An action-only pull and diff showed no drift or conflicts. Local validation does not establish runtime correctness or authorize publication.
- No action was executed, pushed, or published during this verification. Continue using action-scoped commands for action changes; a successful full pull does not authorize a whole-app push.

