EmberKiln — paid launch go-live runbook
The code for paid Studio is fully merged and inert until one flag flips: usage/credits billing (one-time trial → credit packs, no subscription), a render paywall, recorded terms consent, and an activation funnel. This is the exact sequence to take it live. None of it changes closed-beta behavior until step 4.
Model decisions (locked 2026-06-12): usage/credits, no subscription; one-time free trial (not recurring); legal consent gate on every creation path. See docs/ai/INDEX.md → "Studio paid-mode enforcement" for the full code map.
PRs: #545 (enforcement), #546 (paywall UX), #547 (consent), #549 (funnel), #550 (consent on all creation paths).
0. Prerequisites
- Stripe account in live mode (Graphene+ already uses it).
- Access to Railway (backend service) and Vercel (frontend) env vars.
- Super-admin account for the readiness + funnel dashboards.
1. Apply migrations to prod
Apply via your normal migration process (see CI_CD_SETUP.md; MCP does NOT point at prod):
359_studio_terms_acceptance.sql—profiles.studio_terms_{accepted_at,version}360_studio_activation_funnel.sql—profiles.creator_granted_at+story_seasons.published_to_graphene_at(+ backfill + indexes)
2. Stripe — create the two credit-pack prices (live mode)
The packs are defined in code (studio-credit-packs.ts
CREDIT_PACKS). The chars credited come from the code's catalog, not
the Stripe Price — the Price only controls the charge amount, so it
MUST match exactly:
- Product "Studio Credits — 100k characters" → one-time $50.00 → copy Price ID
- Product "Studio Credits — 500k characters" → one-time $200.00 → copy Price ID
3. Set env vars
Stripe vars → Railway only (backend). They're server secrets; the
frontend never calls Stripe directly. If Graphene+ is live, the first
two already exist — only the two STRIPE_PRICE_STUDIO_CREDITS_* are new.
# Railway (backend service)
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_... # the live webhook signing secret
STRIPE_PRICE_STUDIO_CREDITS_SMALL=price_... # the $50 / 100k price
STRIPE_PRICE_STUDIO_CREDITS_LARGE=price_... # the $200 / 500k price
STUDIO_TRIAL_CHARS=20000 # optional; default 20000
Webhook: the same /api/payment/webhook endpoint handles credit packs
(it branches on metadata.kind === 'studio_credit_pack'). Confirm its
live event list includes checkout.session.completed — no second
endpoint needed.
Verify everything green at /dashboard/admin/studio-launch (super-admin).
Cosmetic gotcha: that page's price labels still read "500k / 1.5M" from a pre-repricing era — ignore the label text; the real packs are 100k/$50 and 500k/$200. Only "var is set" matters.
4. Flip the launch flag — both Vercel and Railway
NEXT_PUBLIC_STUDIO_PAID_MODE must be set in both places, then
redeploy both:
- Vercel —
NEXT_PUBLIC_is inlined into the client bundle at build time; the frontend copy/paywall only flips if it's set here + redeployed. - Railway — the backend mirror (
studio-mode.ts) reads it at runtime to switch on trial grants, the 402 preflight, and the consent gate.
NEXT_PUBLIC_STUDIO_PAID_MODE=true
Setting only one = split brain: Vercel-only → UI says "paid" but nothing is enforced; Railway-only → enforcement is live but copy still says beta.
5. Smoke-test on staging first (flag on)
- Sign up fresh → claim creator (
/studio/new) → consent gate appears. - Accept → confirm the 20k trial seeded (
/studio/billingshows balance). - Render past the trial → 402 paywall modal → click a pack →
Stripe Checkout (test card
4242 4242 4242 4242) → land back on/studio/billing, balance bumped by 100k/500k. - In Stripe → Developers → Webhooks → recent deliveries:
checkout.session.completedreturns 200. If the balance didn't move, check the delivery response + backend logs for[studio-credit-packs]. - Export an M4B + publish → check
/dashboard/admin/studio-funnelshows the cohort advancing.
6. Go live + run the pilot
- Repeat steps 2–4 in live mode for prod.
- Run a small instrumented ad pilot to the ICP (mid-list ACX authors
paying narrators $200–400/finished-hour). Read activation at
/dashboard/admin/studio-funnel; slice signup→creator by ad source in PostHog (studio_creator_grantedevent carries first-touch UTM).
Rollback
Set NEXT_PUBLIC_STUDIO_PAID_MODE=false on both Vercel + Railway and
redeploy. The trial/paywall/consent gates all no-op immediately; the DB
columns and any seeded trial credits are harmless when the flag is off.
Existing closed-beta creators are unaffected throughout (they're
grandfathered on the rolling-quota model).
Open / non-code items
- Attorney pass on
/studio/terms— currently self-authored (the page says so). If the reviewed copy changes anything material, bumpSTUDIO_TERMS_VERSIONinstudio-terms.tsto force re-acceptance. Recommended before scaling spend. - Funnel caveat:
creator_granted_at/published_to_graphene_atare backfilled approximately for pre-migration-360 rows; the pilot cohort is exact. signup→creator is all-signups (no studio-intent on signup) — use PostHog UTM cohorts for per-campaign attribution.