Skip to main content
Stripe is integrated in two distinct roles. Most “business settings → Stripe” questions are about Stripe Connect (per business). This page documents the Connect flow in detail and gives a short note on Platform Stripe (SaaS billing) at the end.

Two Stripe roles in the app

RoleWhat it isWhere it shows up
Stripe Connect (per business)The client’s own Stripe account, linked via OAuth.Settings → Data Sources → Stripe, Contacts and Sales grids.
Platform Stripe (SaaS billing)The Agency Engineer’s Stripe account for agency tenants (packages, subscriptions, token top-ups).SaaS Packages, onboarding checkout, /api/webhooks/stripe.
Both use the same STRIPE_SECRET_KEY but different tables. The rest of this page focuses on Connect.

Stripe panel

Open it from a business’s Settings → Data Sources → Stripe. The panel shows:
  • Status badgeNot connected until OAuth completes; Connected with the account display name afterwards.
  • DescriptionConnect this business’s Stripe account to sync customers, products and orders.
  • Status row — Repeats the current state.
  • Connect button — Starts the Stripe Connect OAuth flow.
Once connected, you’ll see the linked account name and a Manual sync button that triggers the same fetch path as the daily scheduler.

What gets stored

Each business has at most one row in the stripe_access table:
FieldDescription
stripe_account_idConnect account ID (acct_…) returned by OAuth.
account_nameDisplay name from Stripe (business name, company, individual name, or email).
date_createdTimestamp the link was saved.
No refresh token is stored. Access goes through the platform’s Stripe Connect app (STRIPE_CONNECT_CLIENT_ID + STRIPE_SECRET_KEY) acting on behalf of stripe_account_id.

How a business connects

  1. Agency appstripeSetting.js calls GET /api/stripe/connect-url (requires business access).
  2. The user is sent to Stripe Connect with scope read_write.
  3. The callback GET /api/stripe/oauth/callback exchanges the code, saves or updates stripe_access, then redirects back to settings with ?stripe=connected or ?stripe=error.
  4. Client portal uses the same pattern with state=forclient:… and client routes (/stripe/connect-url, /stripe/status, /stripe/disconnect).

Environment variables

  • STRIPE_SECRET_KEY
  • STRIPE_CONNECT_CLIENT_ID
  • STRIPE_OAUTH_REDIRECT_URI
  • Plus frontend redirect URLs for agency vs. forclient flows.
Connect the client’s Stripe account, not your agency’s. Each business should point to its own Stripe account so customer and order data stays isolated per client.

What data is pulled

A scheduled job (stripeScheduler, npm run job:stripe) and the Manual sync button both call fetchStripeData, which uses the platform secret with stripeAccount: stripe_account_id (Connect context). Data is synced incrementally by last_successful_date when set:
Stripe APILocal storageMain fields
Productsstripe_productsname, description, active, metadata
Pricesstripe_pricesamount, currency, recurring, product link
Customersstripe_customersemail, name, metadata
Payment intentsstripe_orders (stripe_type: payment_intent)amount, status, customer
Invoices (+ line items)stripe_invoices, stripe_invoice_line_itemsamounts, status, PDF URLs, subscription
Checkout sessions (+ line items)stripe_checkout_sessions, stripe_checkout_line_itemstotals, payment status, customer email
The account display name is refreshed from accounts.retrieve on each sync.

Disconnect and clear

DELETE /api/stripe/:businessId removes stripe_access plus the business’s products, orders, customers, and sync jobs. It does not clear every Stripe-related table — invoices and checkout sessions may remain unless cleared elsewhere.

Revoked access

The Connect webhook account.application.deauthorized clears stripe_access and marks the job failed, so the settings UI surfaces a reconnect prompt.

How the data is used

Contacts and Sales UI

Endpoint / ComponentPurpose
GET contacts/stripe-customersList customers with total spent, order count, last purchase, currency; optional match to GHL contact by email (powers the Stripe customers tab in SalesGrid).
POST contacts/stripe-summary-by-emailsBatch lookup used by Leads, Calls, and Sales grids.
StripeColumns.jsAdds Stripe Spent and Stripe Orders columns to the grids when Stripe is connected and there is matching email data.
stripeByEmailModel.getStripeInfoByEmailPowers per-email Stripe detail in contact history (customers, payments, invoices, checkout).
Spend is deduplicated across invoices, checkout sessions, and standalone payment intents so the same payment never double-counts.

Settings

  • Connection status and account name shown at the top of the Stripe panel.
  • Manual sync (ManualSyncBlock type="stripe") triggers the same fetch path as the scheduler — useful after a fresh connection or after disconnect/reconnect.

Not used (today)

  • Stripe data does not feed the Facebook/ad rules engine (unlike Clarity).
  • ROAS and ad reporting in the Business dashboard still come primarily from GHL and other sources. Wire Stripe in separately if you need it as a revenue source.

Platform Stripe (SaaS billing)

Separate from the per-business Connect account, the platform’s own Stripe account handles:
  • Tenant subscriptions, invoices, package stripe_price_id, and onboarding checkout.
  • Webhooks: checkout.session.completed, customer.subscription.*, invoice.*, credit grants, seat limits, and more.
This bills agencies on The Agency Engineer SaaS, not the client’s ad-business Stripe catalog. Configuration lives under SaaS Packages and the platform webhook handler at /api/webhooks/stripe.

Troubleshooting

SymptomFix
Status stays Not connected after OAuthPop-up blocker may have closed the Stripe window. Retry Connect with pop-ups allowed, or check ?stripe=error in the redirect URL for the cause.
Settings shows a reconnect promptConnect access was revoked (in Stripe or via the account.application.deauthorized webhook). Run Connect again to re-link.
Stripe columns missing in Sales gridEither Stripe isn’t connected for the business, or none of the synced Stripe customers match GHL contact emails.
Stale data after a manual fix in StripeUse Manual sync in the Stripe panel — incremental fetches use last_successful_date.
Customer spend looks doubledSpend should be deduped across invoices, checkout, and payment intents. If it isn’t, capture the customer ID and contact support.

Summary

  • Stripe Connect — one linked acct_… per business via OAuth. Daily and manual sync of customers, products, prices, payments, invoices, and checkout into local tables. Used to enrich Contacts and Sales grids with spend and order counts by email, plus a dedicated Stripe customers view.
  • Platform Stripe — billing your tenants on The Agency Engineer itself.

Next steps

Connect GoHighLevel

Pair Stripe with GHL so customer spend ties back to leads and pipelines.

Business dashboard

See where business KPIs come from today (and where Stripe is not yet wired).