Accelerate CMO · Referral Partner System

Data Model and System Map

Prepared for Melanie Harr-Hughes · July 2026 · by MapMatix

Phase 1, Data Schema & Sync Map (the build blueprint)

How to read this

Each "entity" below is one table in the database. For each, there's a plain-language purpose, then a field list. The custom system is the single source of truth for everything here; GoHighLevel (GHL) holds its own copy of a few facts and we sync a minimal set both ways (see "The GHL sync map").

Money is stored to the cent, in USD. Dates are stored with a timezone. "Link to X" means the field holds the id of a record in another table (how tables connect).


The big picture

One partner refers people. Some of those referrals become paying clients. Each payment a client makes earns the partner 10%. Those 10% amounts get paid out to the partner, over and over, for the life of the client. In record terms:

partner ─┬─ agreement            (their one signed contract)
         │
         └─ referral ─── (converts) ─── client_payment ─── commission ─┐
              (a person       (the referred          (each payment      │
               they               person becomes      the client         │
               introduced)        a client)           makes)             │
                                                                         ▼
                                                                      payout ──► Stripe ──► partner's bank
                                                              (one payout can bundle
                                                               several commissions;
                                                               Melanie releases it)
  • A partner has exactly one agreement (their signed reciprocal referral contract).
  • A partner has many referrals (each person they introduce to Melanie).
  • A referral, once it converts into a paying client, has many client_payments (monthly fees, renewals, expansions, every payment that client makes).
  • Each client_payment creates one commission (10% owed to the partner). Clawbacks are negative entries in the same commission ledger.
  • Commissions get bundled into payouts (the actual Stripe transfers). One payout can cover several commissions, because Melanie releases them on her own schedule.

Phase 1 is Direction A only: partners refer clients IN, Accelerate CMO pays 10% OUT. The contract's reverse direction (Accelerate CMO refers out and collects 10% from a partner) is deliberately not modeled here.


Source-of-truth split (recap)

  • Custom database (this schema) owns: partners, agreements, referrals/attribution, client payments, commissions, payouts, everything the partner sees and all money logic.
  • GHL owns: the sales/referral pipeline, the underlying contact records, and nurture email campaigns.
  • QuickBooks Online owns: client invoicing and payment collection, it is the source of the "invoice paid / payment received" signal (money IN).
  • Stripe: partner payouts (money OUT).
  • Synced (minimal, one owner per fact): see "The sync map" at the end.

Entity 1, partners

Purpose: the referral partner (the person the contract calls "Client"). Their identity, current status, portal login, and how they get paid.

Field Type What it is
id id Unique partner id (internal).
status enum Lifecycle: invitedagreement_sentactivepausedended. Drives portal access and whether payouts run.
first_name text From GHL contact on handoff; editable by partner at intake.
last_name text "
email text Login identity and payout/notice email. Unique.
phone text "
company_name text The partner's business (captured at signing).
full_address text Captured at signing (contract requires it).
relationship_type enum (nullable) Business Owner/Leader, Service Provider, PE Firm, other, her existing segmentation, optional.
ghl_contact_id text Link to the matching GHL contact (the sync key).
stripe_connect_account_id text (nullable) Their Stripe connected account (how money reaches their bank). Bank details live in Stripe, never here.
stripe_onboarding_status enum not_started / pending / complete. Payouts run only when complete. Drives the portal's persistent "complete payout setup" prompt and the GHL Payout Setup Incomplete / Payout Setup Complete tag.
payout_method enum stripe (default). manual_check fallback for a partner who won't use Stripe (recorded, paid outside the system).
tax_form_status enum W-9 / 1099 tracking: not_collected / collected. Stripe Connect collects the W-9 during payout onboarding and can e-file 1099s, we only prompt the partner to finish Stripe's steps (confirm 1099-NEC vs -K treatment with a tax pro).
auth_method enum password or magic_link (portal login).
password_hash text (nullable) If password auth. Never store plaintext.
last_login_at datetime (nullable) "
invited_at datetime When the "Made a Move" handoff fired.
signed_at datetime (nullable) When their agreement completed.
created_at / updated_at datetime Standard.
notes text Free notes (Melanie's admin use).

Entity 2, agreements

Purpose: the one signed reciprocal referral agreement per partner, signed through SignWell. Holds the legal record and the exact values captured at signing.

Field Type What it is
id id Unique agreement id.
partner_id link → partners The partner who signed. One agreement per partner.
signwell_document_id text The SignWell document/envelope id. Signing opens in a new browser tab; SignWell returns the partner to the app to set their password.
status enum sentviewedsignedcompleted. Also voided/declined.
commission_rate number Defaults to 0.10. Stored per-agreement so a future non-standard rate doesn't break the model; today always 10%.
referral_window_days number Defaults to 180. Stored per-agreement for the same reason.
signed_pdf_url text Stored copy of the completed PDF (the partner sees this in their portal).
contract_version text Which template version they signed (GHL template is v2 as of 2026-07-14).
client_full_name text Signer field, as signed (snapshot).
client_company_name text Signer field.
client_full_address text Signer field.
client_phone text Signer field.
client_email text Signer field.
company_signer_name text Who signed for Accelerate CMO (the authorized officer, e.g. Melanie).
executed_date date The "Executed this ___" date on the contract.
sent_at / signed_at / completed_at datetime Timestamps of the signing lifecycle.
reminder_count number Signing reminders sent so far. Cadence: day 2, day 7.
last_reminder_at datetime (nullable) When the last reminder went out (Melanie is notified each time).
escalated_at datetime (nullable) Day-8 escalation to Melanie to follow up personally.

Entity 3, referrals

Purpose: a person a partner introduces to Accelerate CMO, tracked from introduction through (hopefully) becoming a paying client. This is where attribution and the 180-day window live. When a referral converts, this same record carries the client relationship; each payment hangs off it.

Field Type What it is
id id Unique referral id.
partner_id link → partners Who made the introduction (the attribution).
referred_name text The prospective client's name.
referred_email text (nullable) Minimal contact info.
referred_company text (nullable) "
ghl_contact_id text (nullable) The lead's GHL contact, if they live in GHL.
ghl_opportunity_id text (nullable) The lead's GHL Sales Pipeline opportunity, if any.
status enum submittedacknowledged → then either converted (became a client), rejected, or expired.
acknowledged_date date (nullable) When Melanie acknowledged the referral in writing. This starts the 180-day clock (per contract Section 3).
window_expires_on date (derived) acknowledged_date + referral_window_days (180).
window_override enum + date (nullable) Melanie's manual control: none (default, strict), extended_to <date>, or waived (always eligible). Implements the strict-by-default-with-override decision.
eligibility enum (derived) eligible (converted within window, or override), ineligible_expired (no conversion by the window and no override). Drives whether commissions can accrue.
is_business_opportunity yes/no Whether it meets the Section 5 criteria (see below). Set when it converts.
qualifier_flags checklist The Section 5 conditions: full initial payment received; not a pre-existing customer; not in active negotiation before the referral; not referred by another party; lawful. Recorded for the record and disputes.
rejection_reason text (nullable) If rejected (existing customer, in active negotiation, didn't meet requirements).
client_start_date date (nullable) When the referred client signed with Accelerate CMO (their engagement start). Set on conversion.
client_status enum prospect (not yet a client), active_client, ended.
client_ended_date date (nullable) When the client relationship ended (commissions stop accruing after this).
lifetime_value_cached money Running sum of this referral's client payments (for fast portal display; the payments table is the truth).
created_at / updated_at datetime Standard.

Note on renewals/expansions: the contract pays 10% of Lifetime Value including renewals, so a renewal or expanded scope is simply another client_payment on the same referral, not a new referral. (Edge case to confirm: if a partner separately re-refers the same company for a distinct new engagement later, do we treat it as a new referral? Flagged in open questions.)


Entity 4, client_payments

Purpose: each payment a converted referral (the client) actually makes to Accelerate CMO. This is the event that earns the partner their 10%. It exists whether the client paid digitally or by check.

Field Type What it is
id id Unique payment id.
referral_id link → referrals Which client relationship this payment belongs to.
partner_id link → partners Denormalized for fast lookups (always matches the referral's partner).
gross_amount money The gross revenue of this payment (contract defines gross = total contract value before any expenses/fees/costs). The 10% is calculated on this.
payment_date date When the client paid.
received_status enum expected (invoice out, not yet paid) → received (confirmed). Only received earns a commission.
received_via enum qbo_auto (client paid electronically; QBO recorded it itself) or qbo_manual_entry (Melanie recorded the payment in QBO by hand, checks and anything QBO can't auto-detect). Both arrive via the same QBO event; the admin screen never marks a payment received.
confirmed_by text (nullable) Who recorded the payment in QBO, if manually entered (from the QBO event data).
received_at datetime (nullable) When it was marked received (the moment the commission becomes payable).
source_reference text (nullable) QBO invoice id / QBO payment id / check number, for reconciliation.
is_initial_payment yes/no Marks the client's first payment (Section 5 requires the "full agreed-upon initial payment" for the referral to qualify).
created_at datetime Standard.

Entity 5, commissions (the commission + clawback ledger)

Purpose: the running ledger of what the partner is owed. One positive entry per received client payment (10% of its gross). Clawbacks are negative entries. Grouping entries into a payout is how they get paid.

Field Type What it is
id id Unique ledger entry id.
partner_id link → partners Whose ledger.
referral_id link → referrals Which client relationship it came from.
client_payment_id link → client_payments (nullable) The payment that generated it (null for a standalone clawback adjustment).
type enum commission (positive) or clawback (negative).
amount money Signed amount: +10% of the payment's gross for a commission, negative for a clawback.
rate number The rate applied (0.10), copied from the agreement at time of accrual.
status enum payable (owed, not yet released) → released (in a payout, sending) → paid. Also on_hold and void.
payout_id link → payouts (nullable) Which payout paid it. Null while still payable.
clawback_reason text (nullable) For clawbacks: refund or client cancellation within 60 days of client start (contract Section 3A).
accrued_at datetime When it hit the ledger.

Clawback rule (Section 3A): if a client is refunded or the engagement is terminated within 60 days of that client's start date, Accelerate CMO may deduct the corresponding referral fee from any future payout. In data terms: create a negative clawback entry; it nets against the partner's next payout. If the partner has no upcoming payouts to net against, the balance goes negative and carries forward (flagged in open questions for how Melanie wants to handle a partner who's already been paid).


Entity 6, payouts

Purpose: an actual transfer of money to a partner via Stripe. Bundles one or more payable commission entries. Melanie releases these on her own schedule (or sets auto-release); the contract allows 60 days from each client payment.

Field Type What it is
id id Unique payout id.
partner_id link → partners Who's being paid.
amount money Net sum of the commission entries included (commissions minus any clawbacks).
status enum queued (payable, awaiting release) → releasedprocessing (Stripe) → paid, or failed.
release_mode enum manual (Melanie clicked release) or auto (she set payouts to auto-release on payment confirmation).
released_by text (nullable) Who released it, if manual.
stripe_transfer_id text (nullable) The Stripe payout/transfer id.
fee_statement document/JSON The Section 6 statement: the list of commissions included and how each 10% was calculated. Generated at payout time; the partner and Melanie can both see it.
included_commission_ids links → commissions Which ledger entries this payout settled.
released_at / paid_at / failed_at datetime Lifecycle timestamps.
failure_reason text (nullable) If Stripe rejected it.

Entity 7, event_log

Purpose: an append-only record of everything that happened (webhooks received, statuses changed, syncs to/from GHL, payouts released). Needed because this is a money system, and the contract even references "real-time tracking of referral activity." Also the backbone for debugging syncs.

Field Type What it is
id id Unique event id.
entity_type / entity_id text What the event is about (partner, referral, payout, etc.).
event text e.g. partner.invited, agreement.completed, payment.received, commission.accrued, payout.released, ghl.sync_out, ghl.webhook_in.
source enum ghl_webhook, signwell_webhook, stripe_webhook, admin_action, system.
payload JSON The relevant details / raw webhook body.
created_at datetime When it happened.

The GHL sync map (exact)

Only these facts cross the boundary. One owner per fact; no fragile full mirroring.

Into the custom app: 1. [GHL] Partner reaches "Made a Move (attended workshop / Made Intro)" → GHL workflow fires an outbound webhook → custom app creates the partners record (pre-filled from GHL standard fields) and starts onboarding (sends the SignWell agreement, which opens in a new tab). The single wire that kicks off the partner experience. Trigger = entering the stage; manual jumps count. 1b. [GHL] Melanie marks a lead's "Referring Partner" field → GHL workflow webhook → custom app matches the value to an active partner and creates + acknowledges the referrals record (the field-set IS the acknowledgment; the 180-day clock starts that day). No manual referral entry exists in the admin screen. 2. [QBO] A client invoice is paid → QuickBooks Online "invoice paid" webhook → custom app records a client_payments row as received for the matching referral, making the commission payable. Checks QBO can't auto-detect are recorded by Melanie in QBO (never in the admin screen), which fires the same webhook, one door for all money in. 3. [GHL] A referred deal closes (won/lost + close date) → custom app converts or expires the referral and records whether the close fell inside or outside the 180-day window.

Out to GHL: 1. Partner signs + becomes active → tag the GHL contact Active Referral Partner; the card moves to Signed Referral Agreement at contract completion, then on to Nurturing at portal activation (enrolls the nurture campaign). Stage ids are wired from her live pipeline. 2. Partner status change (paused / ended) → update the tag. 3. Payout-setup status → tag Payout Setup Incomplete / Payout Setup Complete as the partner's Stripe onboarding changes.

QBO ↔ GHL: a paid QBO invoice also marks the corresponding GHL deal paid, so Melanie's sales view stays current.

GHL custom fields needed: GHL has zero custom fields today. The build creates five: Referring Partner (a DROPDOWN kept in sync with active partners, setting it is also what creates the referral, per rev 4), Referral Acknowledged Date, Referral Window Expires, Referral Window Outcome, and Referral Last Payment Date. Pending the custom-field edit scope on the API token (one-time setup).

Not synced: the referral / commission / payout ledger. It lives only in the custom database (GHL's affiliate manager is deliberately not used, see the architecture memory and map).

GHL contact fields available for pre-fill

Confirmed live 2026-07-17: her GHL has zero custom fields, so the handoff pre-fills a partner record from GHL's standard contact fields only: firstName, lastName, email, phone, companyName, address1 / city / state / postalCode / country, source, tags, assignedTo, website. Everything partner-specific (payout details, tax status, signed-contract values) is collected by the custom tool at intake/signing, not read from GHL.


Key contract rules encoded (traceability)

Rule Where it lives in this schema
10% of gross, per payment commissions.amount = client_payments.gross_amount × agreements.commission_rate
Recurring for life of client, incl. renewals Many client_payments per referral; commissions accrue on each
Gross = total contract value before expenses client_payments.gross_amount is defined as gross
180-day window, strict by default referrals.acknowledged_date + referral_window_days, eligibility derived
...with Melanie's override referrals.window_override (extended_to / waived)
60-day payment window Payout can be released any time within 60 days of client_payments.received_at; not enforced as a block, surfaced as a due-date in admin
Clawback within 60 days of client start Negative commissions entry (type = clawback), nets against next payout
Fee statement per payment/payout (Section 6) payouts.fee_statement
Business Opportunity qualification (Section 5) referrals.is_business_opportunity + qualifier_flags
Payout to partner's bank partners.stripe_connect_account_id, payouts.stripe_transfer_id