# ============================================================ # REQUIRED — the app won't start without these. # ============================================================ # Supabase (Project Settings → API) NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key # Supabase service-role key. Bypasses RLS; used only by server-side # routes (the webhook and automation engine). Keep this secret — # never paste it into client code. SUPABASE_SERVICE_ROLE_KEY=your-service-role-key # WhatsApp token encryption (64 hex chars = 32 bytes, AES-256-GCM). # Generate with: # node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" # Rotating this value orphans every token encrypted under the previous # key — users have to re-save their WhatsApp settings to reconnect. ENCRYPTION_KEY=your-64-char-hex-key-here # Meta App Secret (Meta for Developers → App Settings → Basic). # Verifies the HMAC-SHA256 signature on every inbound webhook POST. # Required — without it the webhook rejects every request. META_APP_SECRET=your-meta-app-secret # ============================================================ # RECOMMENDED — safe defaults exist but you'll want to set these. # ============================================================ # Canonical public URL of this deployment (scheme + host, no trailing # slash). Used for the sitemap and OG images. Routes that need a # self-referential URL (invite links from /api/account/invitations, # any future email-bound link) derive the origin from the request # itself, so this variable is *only* needed when the request-derived # origin would be wrong — e.g. when generating links from a cron job # or a background worker that has no incoming request. NEXT_PUBLIC_SITE_URL=https://crm.example.com # ============================================================ # OPTIONAL — only needed if you use the feature. # ============================================================ # Defense-in-depth allow-list for the hostnames that # /api/account/invitations is willing to publish in invite URLs. # Comma-separated, no scheme, no port (just hostnames). # # Why this exists: when NEXT_PUBLIC_SITE_URL is unset, invite URLs # are derived from the incoming request's `Host` / `X-Forwarded- # Host` header. On a typical proxied deploy the proxy sets these # to your canonical hostname and they're trustworthy. On a *bare* # deployment exposed to the public internet, an attacker could # POST to the API directly with a spoofed `Host: phishing.example` # and receive an invite URL pointing at their site. # # When this var is set, hostnames not on the list are rejected # (the request falls through to the wacrm.tech fallback with a # console.warn). When unset, behavior is unchanged from earlier # versions — the request-derived host is trusted. # # Most operators don't need this: setting NEXT_PUBLIC_SITE_URL to # your canonical URL already pins invite links there. This is for # operators who want belt-and-braces or run multi-tenant setups # where one app instance serves several hostnames. # # Example: # ALLOWED_INVITE_HOSTS=crm.example.com,crm-staging.example.com # Shared secret protecting GET /api/automations/cron. Required if you # use Wait steps in automations (a scheduled pinger drains pending # executions). Generate any long random string: # openssl rand -hex 32 # See docs/automations-and-cron.md. # AUTOMATION_CRON_SECRET=generate-a-long-random-string # Meta App ID (Meta for Developers → App Settings → Basic). Required to # create/edit message templates with an IMAGE header: Meta only accepts a # Resumable-Upload media handle (not a plain URL) as the header sample, and # that upload is app-scoped. Without it, image-header template submission # returns a clear error; text/body-only templates are unaffected. Pair # with META_APP_SECRET. # META_APP_ID=your-meta-app-id # When "true", POST /api/whatsapp/templates/submit skips the Meta call # and stores the row with a synthetic `dry-run-` meta_template_id. # Set this in CI and local development so you can exercise the full # template UI without a real WABA. Leave unset (or "false") in prod. # WHATSAPP_TEMPLATES_DRY_RUN=true