gifted-hq.com
Marketing, coaching commerce, store, partner portals, and operator tools.
- Brand site + team profiles
- Store, subscriptions, partner portals
- Coach products, templates, affiliates
- Sanity Studio + Stripe product sync
Two live sites for one business. HQ runs the brand and the store, Academics runs the courses, and a customer only ever sees one account.
Marketing, coaching commerce, store, partner portals, and operator tools.
Certification courses, checkout, access control, progress, and admin.
Editable business content, coach profiles, merch, training templates, affiliates.
Courses, purchase options, enrollments, quizzes, lesson progress, webhook dedupe.
I can turn an existing operator-run business into production software without flattening the business rules.
Payments, refunds, and course access each break differently, and someone signed in on one site has to already be signed in on the other.
Both sites are live. One Clerk identity covers them, Stripe webhooks settle every purchase, and Postgres is the only thing that decides who can open a course.
Two live websites, one business. HQ runs the public brand, coaching, store, partner portals, and operator tools. Academics runs courses, quizzes, progress, payment plans, and admin. Customers still get one account and one billing relationship.
GIFTED was already a real business. The old digital footprint had the usual founder-operator problem: commerce in one place, courses in another, content edits somewhere else, and support stitched together with memory, spreadsheets, and inbox search.
The rebuild had to be an operating system, not a nicer landing page. It had to handle checkout, fulfillment, refunds, payment plans, and course access for live customers, with admin screens good enough that nobody has to sit and watch them.
Live business software punishes optimistic code. Stripe can retry a webhook. A customer can refresh a success page. A payment plan can renew before a support ticket is closed. A refund should revoke access without corrupting order history. A course user may start on Academics and authenticate through HQ.
I built around those failure modes. HQ uses Sanity for editable business content and syncs publish events into Stripe. Academics uses Postgres for access truth. Webhooks settle the transaction; the interface only reports what already happened.
Sanity product, template, coach, affiliate, or store item
Webhook resolves Stripe product and price IDs, then writes them back
Customer pays through Stripe with Clerk identity attached
Webhook sends email, updates metadata, inventory, Shippo, or downloads
Course sections, lessons, videos, documents, quizzes, purchase options
Stripe Checkout carries course, user, option, and attribution metadata
WebhookEvent dedupe, then Enrollment upsert in Postgres
Lesson access follows enrollment status and payment-plan progress
HQ and Academics are separate Next.js apps on separate domains. They share one Clerk user pool; Academics is the satellite and HQ is the auth home.
Coaching, store, and course access fail in different ways. Splitting the apps keeps those risks separate while customers still use one account.
Auth setup is fussier: satellite DNS, redirect origins, two Vercel envs, two webhook surfaces, and support copy for cross-domain sign-in.
Stripe webhooks change state. HQ locks checkout with Upstash Redis and stamps metadata. Academics stores processed event IDs before enrollments, payment-plan updates, refunds, or receipts.
Retries are normal in a live business. Payments, refunds, fulfillment, and access have to survive them without crediting the wrong thing twice.
Every handler needs idempotency, retry behavior, and a recovery path after Stripe has already accepted the charge.
HQ keeps its merchandising and marketing content in Sanity, where it can be edited without a deploy. Academics keeps courses, quizzes, and enrollment state in Postgres, where it can be queried and constrained.
HQ needs editable business content. Academics needs relational guarantees. Forcing both into one CMS would make the course platform worse.
Operators need to know the owner of each truth: Sanity, Stripe, Postgres, Clerk, or Bunny. Clean boundaries come with training overhead.
Want the walkthrough, or the parts that aren’t written up yet?
jakerosow@gmail.com