Skip to main content

White-Label Setup Guide

Deploy a fully branded instance of Cronozen under your own domain. White-label partners get their own domain, branding, landing pages, and isolated data — all powered by the Cronozen platform.

Architecture Overview

Customer visits brand.co.kr


   CloudFlare Worker
   (proxy + rewrite)


   cronozen.com ECS
   (multi-tenant app)

    ┌────┴────┐
    │ Routing │──▶ /brand-slug/* (tenant pages)
    └────┬────┘

    ┌────┴────┐
    │  Auth   │──▶ auth.cronozen.com (Central SSO)
    └────┬────┘

    ┌────┴────┐
    │  Data   │──▶ Row-level isolation (center_id)
    └─────────┘
White-label domains are reverse proxies to cronozen.com. The CloudFlare Worker handles domain rewriting, SEO meta tag substitution, and GA4 tracking ID replacement. Authentication flows through Central Auth (auth.cronozen.com) with brand-specific redirect URIs.

Prerequisites

Before starting, ensure you have:
  • CloudFlare account access (API token with DNS + Workers permissions)
  • AWS CLI configured (for database access via Lambda)
  • Access to the thearound-ops repository
  • The partner’s domain registered and DNS pointed to CloudFlare

Phase 1: Database Setup

1.1 Create White-Label Agreement

Register the partner in the whitelabel_agreements table. This can be done via the Admin API or a provisioning script. Via Admin API:
POST /api/admin/partners/onboard
{
  "partnerName": "Brand Name",
  "partnerDomain": "brand-slug",
  "centerName": "Brand Center",
  "centerType": "REHAB_CENTER",
  "adminName": "Admin Name",
  "adminEmail": "admin@brand.co.kr",
  "revenueShareRate": 15,
  "contractStartDate": "2026-04-01"
}
This single API call creates:
  • whitelabel_agreements record (partner contract)
  • centers record (tenant)
  • center_tenant_mapping (cross-service linking)
  • unified_actors (admin account)
  • center_memberships (admin membership)

1.2 Configure Features & Branding

Update the white-label agreement with branding and feature flags:
{
  "branding_logo": "https://cdn.brand.co.kr/logo.svg",
  "branding_primary_color": "#1A5276",
  "branding_favicon": "https://cdn.brand.co.kr/favicon.ico",
  "branding_og_image": "https://cdn.brand.co.kr/og.png",
  "landing_hero_title": "Brand Tagline",
  "seo_title": "Brand Name - Service Description",
  "seo_description": "Meta description for search engines",
  "features": {
    "survey": true,
    "voucher": true,
    "matching": true,
    "case_management": false
  },
  "enabled_domains": ["rehab"],
  "public_signup_enabled": false,
  "public_page_enabled": true
}
Set public_signup_enabled: false initially. Enable only after testing the full auth flow.

Phase 2: Application Configuration

Three config files must be updated in the thearound-ops repository.

2.1 Domain Registry

File: config/domains.config.ts
'brand.co.kr': {
  domain: 'brand.co.kr',
  centerDomain: 'brand-slug',
  centerName: 'Brand Name',
  type: 'REHAB_CENTER',
  theme: {
    primaryColor: '#1A5276',
    logo: 'https://cdn.brand.co.kr/logo.svg',
    favicon: 'https://cdn.brand.co.kr/favicon.ico'
  }
}

2.2 Proxy Constants

File: src/lib/proxy/constants.ts Add three entries:
// 1. Custom domain mapping
customDomains['brand.co.kr'] = { type: 'rehab', slug: 'brand-slug' }

// 2. Auth client mapping
CUSTOM_DOMAIN_AUTH_CLIENTS['brand.co.kr'] = 'brand'

// 3. Public page segments (opt-in rewrite policy)
// Only listed segments are rewritten; all others pass through safely
CUSTOM_DOMAIN_REWRITE_SEGMENTS['rehab'] = new Set([
  '',                    // root landing
  'pricing',             // pricing page
  'marketplace',         // marketplace
  'terms', 'privacy',   // legal pages
])
The rewrite policy is opt-in by default. Only segments explicitly listed in CUSTOM_DOMAIN_REWRITE_SEGMENTS are proxied. This prevents accidental exposure of internal pages on the custom domain.

2.3 Auth Client Registration

File: config/auth-clients.config.ts
brand: {
  name: 'Brand Name',
  allowedRedirectUris: [
    'https://brand.co.kr/api/auth/central-callback'
  ],
  usesCookieSharing: false  // host-only cookies (recommended)
}
Set usesCookieSharing: false for white-label domains. This ensures cookies are scoped to the brand domain only, preventing cross-domain leakage.

Phase 3: CloudFlare Setup

3.1 DNS Configuration

Add the custom domain zone to CloudFlare, then create DNS records:
# A record pointing to AWS ALB
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -d '{
    "type": "A",
    "name": "brand.co.kr",
    "content": "<ALB_ELASTIC_IP>",
    "proxied": true
  }'

3.2 CloudFlare Worker

Create a Worker that proxies requests from brand.co.kr to cronozen.com: Key Worker responsibilities:
  • Root path (/) rewrite to /{brand-slug} landing page
  • Auth paths (/auth, /login) rewrite to /{brand-slug}/login
  • Static files (/sitemap.xml, /robots.txt) served directly
  • HTMLRewriter transformations:
    • GA4 tracking ID replacement (cronozen ID -> brand ID)
    • OG/Twitter meta tag domain substitution
    • Canonical link rewrite
    • Remove noindex meta tags (enable SEO for brand domain)
  • CSP header rewrite (preserve auth.cronozen.com, replace other cronozen refs)
Wrangler configuration:
name = "brand-proxy"
main = "brand-worker.js"
compatibility_date = "2026-01-09"

[[routes]]
pattern = "brand.co.kr/*"
zone_name = "brand.co.kr"

[[routes]]
pattern = "www.brand.co.kr/*"
zone_name = "brand.co.kr"
Deploy:
wrangler deploy

3.3 Legacy Domain Redirects (Optional)

If the partner has an old domain that should redirect to the new canonical domain:
// In the Worker
if (request.headers.get('host') === 'old-brand.com') {
  const url = new URL(request.url);
  url.hostname = 'brand.co.kr';
  return Response.redirect(url.toString(), 301);
}

Phase 4: Testing

Authentication Flow

1. User visits brand.co.kr
2. Clicks "Login" → redirected to auth.cronozen.com
3. Enters credentials → Central Auth validates
4. Redirected to brand.co.kr/api/auth/central-callback
5. JWT issued with centerDomain = "brand-slug"
6. User lands on brand.co.kr dashboard

Verification Checklist

TestExpected Result
brand.co.kr loads landing pageBrand logo, colors, content
brand.co.kr/login redirects to Central Authauth.cronozen.com with client_id=brand
Login completes successfullyRedirect back to brand.co.kr
JWT contains correct centerDomain"centerDomain": "brand-slug"
Data isolationOnly brand center’s data visible
cronozen.com/brand-slug returns noindexrobots meta tag present
brand.co.kr does NOT have noindexrobots meta tag removed by Worker
GA4 events fire with brand tracking IDCheck GA4 real-time

Phase 5: SEO Configuration

Philosophy

  • Platform SEO = cronozen.com (platform features, pricing)
  • Brand SEO = brand.co.kr (customer-facing, indexed by Google)
  • Internal paths (cronozen.com/brand-slug) are noindex to prevent duplicate content

Canonical URLs

The CloudFlare Worker rewrites canonical links:
<!-- Before (cronozen.com) -->
<link rel="canonical" href="https://cronozen.com/brand-slug/pricing" />

<!-- After (brand.co.kr) -->
<link rel="canonical" href="https://brand.co.kr/pricing" />

Sitemap

Each brand domain serves its own sitemap.xml via the CloudFlare Worker, containing only the public pages relevant to that brand.

White-Label API

GET /api/whitelabel//config

Retrieve partner branding and feature configuration at runtime.
{
  "branding": {
    "logo": "https://cdn.brand.co.kr/logo.svg",
    "primaryColor": "#1A5276",
    "favicon": "https://cdn.brand.co.kr/favicon.ico"
  },
  "features": {
    "survey": true,
    "voucher": true,
    "matching": true
  },
  "enabled_domains": ["rehab"],
  "terminology": {
    "center": "Center",
    "mentee": "Client"
  }
}

Data Model

whitelabel_agreements
├── partner_domain (unique)
├── contract_status (ACTIVE / INACTIVE)
├── business_model (PUBLIC / PRIVATE)
├── revenue_share_rate (0-100)
├── branding_* (logo, colors, favicon, OG image)
├── landing_* (hero title, features, testimonials)
├── seo_* (title, description, keywords)
├── features (JSON: survey, voucher, matching, etc.)
├── enabled_domains (String[]: rehab, edu, welfare)
└── Relations
    ├── centers (1:N)
    └── partner_memberships (1:N)

Cross-Service Integration

White-label tenants can optionally enable CMS (blog) and LMS (learning) services:
ServiceMapping FieldDomain Pattern
OPScenters.center_domainbrand.co.kr (via Worker)
CMScenter_tenant_mapping.cms_tenant_idblog.cronozen.com/{slug}
LMScenter_tenant_mapping.lms_tenant_id{slug}.learn.cronozen.com
Enable via center_tenant_mapping:
UPDATE center_tenant_mapping
SET cms_enabled = true, lms_enabled = true
WHERE center_domain = 'brand-slug';

Troubleshooting

Common Issues

IssueCauseFix
Login redirects to cronozen.com instead of brand domainAuth client not registeredAdd to auth-clients.config.ts
404 on brand.co.kr/pricingSegment not in rewrite listAdd to CUSTOM_DOMAIN_REWRITE_SEGMENTS
Duplicate content in GoogleMissing noindex on internal pathCheck robots.ts in OPS layout
Wrong GA4 dataWorker not replacing tracking IDUpdate GA4Rewriter in Worker
CORS errors on API callsCSP header not rewrittenCheck Worker CSP rewrite logic