> ## Documentation Index
> Fetch the complete documentation index at: https://docs.myfundingmachine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Onboarding payment welcome

# Payment welcome cutover

Prepared for code deployment. The welcome remains inactive until the live cutover
below. No workflow publication, customer send, or survey submission was performed
during the October 2, 2026 review.

## Verified existing configuration

* Agency Location: `HUjFTz0AbjoNOcWuS1h7`.
* Published **FM Closers Signed Agreement Workflows**:
  `cdc89371-97e7-4a7f-a7c3-f05d1e781485`. Annual/monthly document-completed triggers
  currently send the welcome email and its companion SMS. Other actions include
  pipeline updates, Slack notification, contact copying, and a member tag. Leave
  those actions in place. The copy destination was not established by the UI.
* Existing template: `69bc798be2e963a9d5a3e2dd`. Its button targets the public
  onboarding root. Its click condition adds agency tag **onboarding link clicked**.
  The user approved retiring that click tag on October 2, 2026. The cloud welcome
  records opens without propagating the agency tag. Existing tag history is retained;
  Operations contact IDs must never be used as agency IDs.
* Operations Location: `ReoQmuh4ooNuHeE5stlj`.
* Published **Create Onboarding Opp on New Subscription Payment**:
  `aa7de117-009f-43f6-80fd-013bc8bbbceb`. Both triggers require Payment status Success.
  Products are **Funding Machine SaaS - V2 + Setup** and **MFM SaaS - V2 + Leads + Setup**.
  Find Opportunity matches the Onboarding pipeline and contact full name. It does
  not establish contact identity; the app separately verifies the returned card's contact ID.
* **Z - Onboarding Funnel**, page `mAfOovWqrNep7g3bdGAt`, publishes
  `https://onboarding.myfundingmachine.com/pre-onboarding-step-1-page`.
  It embeds **General | Pre Onboarding Form - V2**, survey `CzZVhTZnyHwIuT4nNcDo`.
  The funnel overrides the survey's thank-you message with a redirect to
  `{{custom_values.schedule_tech_call_url}}` and existing first/last name, phone,
  email, agency name, agency owner email, and relationship ID parameters.
  Sticky Contact and partial-submission contact creation were off. Preserve the
  survey and this redirect. No contact-ID binding contract was verified.
  A browser visit with only synthetic first/last-name query values confirmed those
  fields prefill in this exact published page. No contact details or survey answers
  were submitted; full submission-to-contact matching still needs a designated test.

## Implementation

`POST /api/webhooks/ghl-workflow/onboarding-payment` authenticates a dedicated
`ONBOARDING_PAYMENT_WEBHOOK_TOKEN` bearer before reading its bounded JSON body:

```json theme={null}
{
  "locationId": "ReoQmuh4ooNuHeE5stlj",
  "transactionId": "CRM_TRANSACTION_ID",
  "opportunityId": "FOUND_OR_CREATED_ONBOARDING_OPPORTUNITY_ID"
}
```

Use the workflow variable picker and a reviewed test execution to establish the
exact transaction and found/created opportunity variables. Do not substitute Stripe
charge IDs or guess merge-tag names. Both opportunity branches need the webhook.
The endpoint returns 202 only after durable enrollment; verification failures return
503 for visible staff review/retry. Do not assume the workflow retries automatically.

Provider reads verify the exact transaction, completed order, approved product ID,
live/test mode, positive non-refunded amount, new order and opportunity dates, open
onboarding pipeline, and Operations contact. Manual payments, signature events,
failed payments, old orders/cards, and zero-dollar trials do not enroll. The welcome
is deduplicated per Operations contact; a different episode requires staff review.
No product-account mapping is required before this initial welcome. Later messages
retain their existing stricter product-account mapping checks.

Read-only production shape checks confirmed successful order transactions and matching
order/contact/Location IDs. The transaction-by-ID endpoint returned a single-element
array; the CRM client now normalizes only that endpoint and rejects ambiguous arrays.
Orders returned objects with `items[].product._id`. No customer payload was saved.

The existing cloud cron processes at most five welcome records per pass. It rechecks
provider state, current contact DND, hold status, and CRM form-submitted date. The
first pass sends E1; a later pass sends `W1-welcome-sms`. Existing durable claims,
receipts, pause fencing and uncertain-send reconciliation remain authoritative.
The app controls sending; Operations CRM conversations remain the delivery provider.

The email/SMS use a 30-day HMAC link. The redirect validates the enrollment,
generation, expiry and pause state, reads the scoped contact, records its first open,
and redirects to the existing funnel with encoded prefill plus UTM attribution.
It does not send a raw contact-ID override or forward the bearer token to the funnel.
Email/phone matching remains with the CRM. It never treats a click as a submission;
link scanners may also generate opens. Prefill appears in the destination URL/history.

## Slack before product provisioning

Enrollment now durably queues `onboardingSlackPaid.refresh`. The existing Slack
reconciliation cron processes at most five due payment enrollments per pass. The
payment worker rereads transaction/order/contact/opportunity evidence and hold status
before claiming and immediately before each provider write. It requires send mode,
enabled persisted live state, `ONBOARDING_ENGINE_LIVE_ENABLED=true`, workspace mode,
provisioning and CRM writes. A configured Location pilot list denies pre-product writes.
Compute mode can prepare the welcome for review without an existing channel or provider writes. No new production flag is enabled by deployment.

For the new-paid-only rollout, keep `ONBOARDING_SLACK_PROVISION_ENABLED=false` and
set `ONBOARDING_SLACK_PAID_PROVISION_ENABLED=true` only at the authorized cutover.
The paid switch requires an exact Operations payment enrollment on or after the
policy cutoff, including after a product Location adopts the job. It does not grant
provisioning to older accounts without an eligible enrollment. Turn the paid switch
off to stop new writes while retaining channels and receipts.

FM Bot posts the personalized welcome only after channel membership is confirmed.
Its account link uses the verified product Location. If the member joins before
account linking, `welcomePendingAccountLink` records that the same `welcomeTs` needs
a link update. Updates retry against that message; uncertain initial sends are never
automatically reposted. Legacy messages without the pending flag are preserved.

The onboarding owner specified the customer destination as
`https://app.myfundingmachine.com/v2/location/{locationId}/custom-menu-link/fundingmachine`.
On October 7, 2026, a read-only authenticated browser inspection of an existing customer
tab at this route confirmed the Funding Machine dashboard iframe for the same Location,
including its Onboarding & Setup link. The older `app.leadbuilt.com/.../ugflmc` links
elsewhere are not the requested customer-agency destination. No live send was performed
for this verification.

The existing `onboardingSlackProvisioning` reservation can initially have a `welcomeId`
without `locationId`. That welcome is scoped to Operations; Operations is never stored
as a synthetic customer Location. A unique opportunity reservation and lease serialize
both entry points. Later verified product linking atomically adopts it, preserving
channel ID, recovery name, Operator ID and invitation/welcome receipts. Active leases,
conflicting identities/channels and paused enrollment prevent adoption. Product pauses,
Labs and agency checks remain in force. Pre-product channels never enter account-scoped
support ingestion; humans can still respond in Slack.

With workspace mode enabled, E1 and its SMS wait for confirmed channel/staff setup and
a usable current invitation. They include a signed `/api/onboarding/welcome` link with
`destination=slack`. The redirect uses the same enrollment, generation, expiry and pause
checks, and reads the current approved invitation. It never takes a redirect URL from
the caller, records an intake-form open for a Slack click, or forwards the signed token
to Slack. Pausing or rotating an invitation takes effect on the next click.

For recovery, inspect `onboardingSlackProvisioning.health`, `message`, `nextCheckAt`
and the write-attempt receipts for the exact opportunity. Pre-reservation failures log
the welcome ID, error class and provider HTTP status without response bodies or secrets.
The enrollment's `slackNextCheckAt` retries those failures independently of email timing.
The sweep also initializes at most five older welcome records per pass without changing
send receipts. Once a verified Location job owns retries, the welcome leaves the Slack
queue. Failures before reservation use the same bounded backoff as saved jobs: three
five-minute retries, then 30 minutes, then six hours. Stale failures cannot overwrite
newer progress, active leases, paused enrollments or changed account mappings. Reissue/resume requeues the
welcome's Slack check; neither clears write receipts. Expired enrollments require staff
review/reissue. Already linked jobs continue through the normal product checker.

## Activation prerequisites and exact live changes

1. Click-tag retirement is approved: the replacement welcome does not add the agency
   **onboarding link clicked** tag. Disable the old welcome sends at cutover as below;
   do not delete historical tags or add cross-Location propagation.
2. Deploy the reviewed app and Convex changes while the welcome remains unconfigured.
   This requires explicit production deployment authorization under AGENTS.md.
3. Configure `ONBOARDING_PAYMENT_WEBHOOK_TOKEN` in the app and its matching bearer
   header in the Operations workflow; configure a separate high-entropy
   `ONBOARDING_WELCOME_LINK_SECRET` (at least 32 characters) in Convex. Use the existing
   Operations-scoped `ONBOARDING_ENGINE_LIVE_PIT`; verify payment read scopes and
   conversation send permission. Never print or commit credentials.
4. Pause the existing engine and finish its lease. Preserve its reviewed config,
   imported history and cohort. Call `onboardingWelcomeStore.configure` with an admin
   actor, review reason and policy JSON containing: `mode: "compute"`, a future UTC
   millisecond `startsAt`, `liveMode: true`, exact approved Operations `productIds`,
   `nativeOwnershipEvidence`, verified Operations `senderName`/`senderEmail`, and
   `intakeOrigin` set to the HTTPS app origin. Product IDs and sender address must be
   read from the actual Operations configuration; display names are not IDs. The
   cutoff is immutable once configured and avoids historical backfill.
5. Add the authenticated webhook after finding/creating the card in both branches of
   the exact Operations payment workflow above. Keep the success/product filters.
   Confirm the synthetic/test execution supplies the correct IDs and receives 202.
6. Disable only **Email** and **SMS** in the exact agency signed-agreement workflow.
   Keep the workflow and its other actions. Preserve the linked template as a rollback
   reference. Confirm no enrolled contact remains queued for those sends.
7. Before send mode, confirm workspace settings, the current Never expires invitation,
   bot/staff configuration, provisioning and CRM-write gates. A Location-only pilot
   cannot create a pre-product channel: obtain broader-rollout approval before removing
   that restriction. Rehearse payment → channel with no sub-account → workspace join →
   account linking, confirming one channel and one welcome. Pause/resume, rejected or
   refunded payments, uncertain writes and invitation rotation must also be checked.
8. After compute evidence and an explicitly authorized test send to a designated
   test contact confirm sender, SMS destination, prefill and calendar handoff, pause,
   configure the same policy as `mode: "send"`, and resume the engine. Do not enable
   sending while the old welcome actions can still fire.

The future cutoff, new-order check, and one-contact ledger prevent renewal/backfill
messages; they do not reconcile a welcome previously sent in the agency before
cutover. Review any signed-before-cutover/paid-after-cutover customers and suppress
their enrollment with `manage: pause` before enabling send mode. A historical agency
receipt must never be inferred merely from matching an email address.

## Staff inspection and recovery

* `onboardingWelcomeStore.find({ locationId, contactId })` returns the enrollment ID,
  paid/open/expiry timestamps, pause state and opportunity ID. It excludes form data.
* `onboardingEngineLiveStore.inspect` and `evidence` expose run reports and accepted
  or uncertain communication claims. Existing `resolve` requires a paused engine,
  exact attempt and evidence. Do not turn an uncertain result into a resend without
  confirming the provider did not accept it.
* `onboardingWelcomeStore.manage` accepts `pause`, `resume`, or `reissue` plus admin
  actor and reason. Reissue requires a paused engine with no active lease, invalidates
  the old link and returns the replacement link for controlled delivery. It never
  clears receipts or automatically resends an accepted message. Treat that returned
  link as a bearer credential, not something to put in logs or tickets.
* Rollback: pause the cloud engine and disable both general and paid Slack provisioning first, preserve all enrollment and dispatch history,
  then review affected contacts before restoring native sends. Never run both welcome
  senders simultaneously or bulk-reenroll existing customers.

API references: [Transaction by ID](https://marketplace.gohighlevel.com/docs/ghl/payments/get-transaction-by-id/),
[Order by ID](https://marketplace.gohighlevel.com/docs/ghl/payments/get-order-by-id/).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.