Skip to main content

CRM-confirmed partner referrals

The customer-list API returns leads before commissions exist. Read GET /affiliate-manager/:locationId/affiliates/:affiliateId/customers with Version: v3. The returned contactId is a CRM contact ID; _id is the Affiliate Manager customer ID. meta.count is the full result count, including on an empty page past the end (read-only verified on Whigham Group, 2026-09-09). affiliateCustomerSync stores evidence in partnerReferralCustomers and projects it into partnerReferralEdges with source: "crm". The old URL reconciler cannot overwrite or clear CRM projections. Disabled/deleted records are inactive; absence is reconciled only after all pages succeed with a stable total and no repeated customer IDs. Conflicting active owners are ambiguous. Money remains in the commission and payout caches. Missing cached campaign membership no longer blocks the worker. It reads the same-location affiliate from the CRM: a verified deletion or explicit empty membership list completes an empty scan, while current memberships allow the customer read to continue. Failed, malformed, or foreign-location responses preserve the saved referrals for retry; cache absence alone never clears them.

September API cleanup

Campaign discovery now uses the location’s campaign-list API, including empty campaigns. Detail hydration remains necessary for referral templates. A failed campaign-list read retains the embedded-reference fallback and does not stamp a complete sync; a successful empty result never falls back. Commission status is preserved in the ledger. Only pending and approved rows contribute to unpaid balances; denied, canceled, and unknown rows remain visible in the recorded history. Report columns distinguish paid/unpaid amounts from the gross recorded commission total. The authenticated Marketplace enrollment adapter uses direct affiliate creation when there is no explicit staff workflow override. Hosted CRM forms and native workflows are unchanged. The direct path is tested with synthetic responses; an authorized test enrollment in a non-production CRM location is still needed before enabling its live use. Verify contact association, existing-email profile preservation, prior campaign memberships, and any campaign-triggered messages. Do not use the initial referral backfill as an enrollment test: it only reads existing CRM customer relationships. Each partnerReferralSyncs row is an hourly job with durable offset, generation, attempt, lease, next due time, completion timestamp and error. One action reads one API page. Mutations fence stale workers and atomically save evidence, projection and progress. The five-minute watchdog resumes expired work and retries failures. The authorized affiliate-cache sync enrolls jobs; manual Refresh requests an immediate run. No CRM workflow changes are required.

Initial production rollout (requires human authorization)

Target: Whigham Group, Location ts3Kmj7oyUTjwvx6mKwn. First partner: Christinya Romero, affiliate 6a943c2c7e7772057dcae5e4.
  1. Set the production Convex environment value GHL_PARTNER_REFERRAL_SYNC_LOCATION_IDS=ts3Kmj7oyUTjwvx6mKwn before deploying. The restriction is checked at enrollment and each worker claim.
  2. Deploy the schema/backend first, then the compatible frontend. Do not enable or change the separate affiliate-cache cron as part of this rollout.
  3. Enqueue this partner through the internal mutation prospecting/affiliateCustomerSync:enqueue with:
  4. Read the Location/affiliate job until it is idle with lastCompletedAt populated and no lastError. Confirm the six known CRM leads are matched, visible in directory/detail/contact attribution, and included in a report preview. Do not send a report or trigger a credit pull to validate this.
  5. Confirm commissions and payouts did not change. Observe the next hourly run before expanding the Location allowlist.
Only these rollout steps write production. Local codegen/tests do not recover the six live leads. CRM requests made by the new worker are read-only; the worker writes the app’s cache and schedules its own continuations.

Pausing and diagnosis

To pause all customer relationship jobs, set GHL_PARTNER_REFERRAL_SYNC_LOCATION_IDS=__paused__. An empty value permits all Locations that run affiliate-cache sync. A currently claimed page may finish; subsequent claims obey the restriction. Keep the additive schema on rollback so previously saved CRM projections remain readable and protected. Inspect partnerReferralSyncs by (locationId, ghlAffiliateId) for progress or the retained error. The record UI shows sync delays and assignment conflicts. Invalid/missing IDs, unknown campaign membership, changed totals and malformed responses fail closed. A repeated or inconsistent page restarts after five minutes without clearing saved referrals. Detail contacts paginate in groups of 100 by contact ID. Reports share that reader, with an explicit 10,000-contact safety limit. Existing 500-commission and directory edge scan limits remain bounded; directory counts display + when incomplete. No limit is represented as an exact total.