Skip to content

ONSET Clinic Portal - Railway Deploy Checklist

cd /Users/ryancurry/Developer/onset-web/onset-clinic
railway login
railway link

2) Push environment variables to Railway

Set these in Railway service variables:

  • NEXT_PUBLIC_ONSET_API_URL (legacy: NEXT_PUBLIC_SUPABASE_URL)
  • NEXT_PUBLIC_ONSET_API_ANON_KEY (legacy: NEXT_PUBLIC_SUPABASE_ANON_KEY)
  • NEXT_PUBLIC_APP_URL=https://clinic.theonset.app
  • STRIPE_SECRET_KEY
  • STRIPE_WEBHOOK_SECRET
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
  • STRIPE_SOLO_MONTHLY_PRICE_ID / STRIPE_SOLO_ANNUAL_PRICE_ID
  • STRIPE_PRACTICE_MONTHLY_PRICE_ID / STRIPE_PRACTICE_ANNUAL_PRICE_ID
  • STRIPE_PRACTICE_SEAT_MONTHLY_PRICE_ID / STRIPE_PRACTICE_SEAT_ANNUAL_PRICE_ID
  • STRIPE_FULLSCRIPT_ADDON_MONTHLY_PRICE_ID / STRIPE_FULLSCRIPT_ADDON_ANNUAL_PRICE_ID
  • ONSET_API_SERVICE_ROLE_KEY (legacy: SUPABASE_SERVICE_ROLE_KEY)

Generate the price IDs with npm run stripe:pricing (run once with the Stripe test key, once with the live key). The legacy Pro product/prices have been archived in Stripe and removed from the env. - PATIENT_PII_ENCRYPTION_KEY (base64url-encoded 32-byte AES key — required for patient create/update; generate with node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))") - NOTIFY_FUNCTION_SECRET (shared with Supabase edge function) - SENDGRID_API_KEY (required for patient invite emails and portal Help tool submissions with attachments — copy the same key from Supabase prod Edge Function secrets or SendGrid dashboard) - NOTIFY_FROM_EMAIL (optional, default Onset Clinic <notifications@theonset.app>) - CLINIC_SUPPORT_INBOX_EMAIL (optional, default ryan@theonset.app — destination for portal Help submissions) - CLINIC_SUPPORT_ADMIN_URL (optional — deep link base in support emails; default https://admin.last1.enterprises/admin/onset/support-tickets) - CRON_SECRET (used by /api/cron/weekly-summary — generate a 32+ char random string) - NEXT_PUBLIC_ONSET_APP_URL (optional, defaults to https://theonset.app) - NEXT_PUBLIC_ONSET_IOS_APP_STORE_URL (optional — patient invite handout PDF App Store link; defaults to https://apps.apple.com/us/app/the-onset/id6761314183)

Optional one-shot CLI set (after railway link):

railway variables set \
  NEXT_PUBLIC_ONSET_API_URL="..." \
  NEXT_PUBLIC_ONSET_API_ANON_KEY="..." \
  NEXT_PUBLIC_APP_URL="https://clinic.theonset.app" \
  STRIPE_SECRET_KEY="..." \
  STRIPE_WEBHOOK_SECRET="..." \
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="..." \
  STRIPE_SOLO_MONTHLY_PRICE_ID="..." \
  STRIPE_SOLO_ANNUAL_PRICE_ID="..." \
  STRIPE_PRACTICE_MONTHLY_PRICE_ID="..." \
  STRIPE_PRACTICE_ANNUAL_PRICE_ID="..." \
  STRIPE_PRACTICE_SEAT_MONTHLY_PRICE_ID="..." \
  STRIPE_PRACTICE_SEAT_ANNUAL_PRICE_ID="..." \
  STRIPE_FULLSCRIPT_ADDON_MONTHLY_PRICE_ID="..." \
  STRIPE_FULLSCRIPT_ADDON_ANNUAL_PRICE_ID="..." \
  ONSET_API_SERVICE_ROLE_KEY="..."

3) Deploy

railway up

4) Domain mapping

In Railway service settings:

  • Add custom domain: clinic.theonset.app

In DNS provider:

  • Add the CNAME target that Railway provides for clinic.theonset.app

5) Stripe webhook

In Stripe Dashboard -> Developers -> Webhooks:

  • Endpoint URL: https://clinic.theonset.app/api/stripe/webhook
  • Events (all 5 must be enabled — the handlers exist in src/app/api/stripe/webhook/route.ts):
  • checkout.session.completed — flips practitioner to PRO on first paid checkout
  • customer.subscription.deleted — resets to Free, preserves patient data
  • customer.subscription.updated — syncs tier/cap on any dashboard-driven change or dunning outcome
  • customer.subscription.trial_will_end — fires 3 days before trial end; enqueues a clinic_notifications row so we can email a reminder before the card is auto-charged
  • invoice.payment_failed — enqueues a clinic_notifications row AND sends an immediate SendGrid alert with a Settings → Manage billing deeplink

5a) Stripe Customer Portal

In Stripe Dashboard -> Settings -> Billing -> Customer portal:

  • Account default configuration must exist and be active.
  • Required features: payment method update, invoice history, subscription cancel (mode: at_period_end), customer update (email/name/tax_id/address).
  • "After update" return URL: https://clinic.theonset.app/settings.
  • The /api/billing/portal route uses the account default unless STRIPE_BILLING_PORTAL_CONFIGURATION_ID is set in env.

5b) Stripe trial behavior

  • /api/stripe/checkout creates the Checkout Session with payment_method_collection: "always" — card is required at signup.
  • Default trial is 14 days, granted only on the Practice (top) tier on first checkout (STRIPE_TRIAL_DAYS env override available).
  • trial_settings.end_behavior.missing_payment_method: "cancel" — if the card is removed before trial end, the subscription cancels rather than rolling onto a dead card.

6) Smoke tests

  • https://clinic.theonset.app/login loads.
  • New signup works.
  • /settings shows plan card and upgrade prompt for free-tier.
  • Upgrade buttons redirect to Stripe Checkout.
  • On successful payment, practitioner row updates to:
  • tier = pro
  • max_patients = 999

6.5) Weekly summary email cron (Pro practitioners)

Wire any HTTP cron service to hit the secure trigger on Monday morning UTC.

  • Endpoint: POST https://clinic.theonset.app/api/cron/weekly-summary
  • Required env vars on the clinic portal service:
  • CRON_SECRET — random 32+ char string, shared with the cron caller
  • NOTIFY_FUNCTION_SECRET — already set
  • NEXT_PUBLIC_ONSET_API_URL (or legacy NEXT_PUBLIC_SUPABASE_URL) — already set
  • Authentication options:
  • Authorization: Bearer <CRON_SECRET> (preferred, works with Railway Cron / GitHub Actions / Upstash QStash / external schedulers)
  • ?secret=<CRON_SECRET> plus x-vercel-cron header (for Vercel Cron clones)

Suggested Railway Cron service (separate one-off command service in the same Railway project):

curl -fsSL -X POST \
  -H "Authorization: Bearer $CRON_SECRET" \
  https://clinic.theonset.app/api/cron/weekly-summary

Schedule: 0 13 * * 1 (Monday 1pm UTC ≈ 9am ET / 6am PT).

The endpoint just authenticates the call and forwards eventType: "weekly_summary" to the practitioner-notifications Supabase edge function, which already selects Pro practitioners and composes the email body.

7) End-to-end test plan (manual cross-platform)

  1. Sign up practitioner.
  2. Add patient.
  3. Build + assign protocol (10+ items).
  4. iOS app enters connection code, accepts protocol.
  5. Complete items -> adherence sync appears in clinic portal patient detail.
  6. Share labs in iOS -> labs appear in clinic portal patient detail.
  7. Upgrade to PRO via Stripe -> tier and limits update.