Configuration & secrets.
Every environment variable, secret, binding, OAuth redirect, webhook URL, and cron trigger Concierge expects. All sensitive values are stored as Cloudflare Workers secrets via wrangler secret put; non‑sensitive values live in the [vars] block of wrangler.toml.
Use the Wrangler CLI: wrangler secret put SECRET_NAME. You’ll be prompted to enter the value securely. Secrets are encrypted at rest by Cloudflare and exposed to your Worker as environment variables.
Environment variables
The Worker reads three groups of plain‑text vars: a static default in wrangler.toml, deployment‑specific values injected at deploy time, and optional integration overrides set in the dashboard.
[vars]
| Variable | Description |
|---|---|
ENVIRONMENT | production or development. Dev mode bypasses some auth checks. |
| Variable | Description |
|---|---|
CF_ACCESS_TEAM | Cloudflare Access team name (the subdomain in myteam.cloudflareaccess.com). |
CF_ACCESS_AUD | Cloudflare Access Application Audience (AUD) tag. Configure the Access app with the path your-domain/manage*: no slash before the asterisk, so it covers both the /manage dashboard and every subroute. |
PUBLIC_BASE_URL | Your deployment’s primary URL, e.g. https://concierge.example.com. Used in approval‑digest email links and the email‑domain redirect. |
EMAIL_DOMAIN | Email domain you control; configure MX records manually on its apex and wire it into Cloudflare Email Routing. |
WHATSAPP_WABA_ID | Your WhatsApp Business Account ID. Only required if you’re running WhatsApp. |
WHATSAPP_SIGNUP_CONFIG_ID | Meta Embedded Signup configuration ID for your Meta app. Only required if you’re running WhatsApp. |
The npm deploy script reads these from process.env and passes them as --var flags. Build env vars persist across deploys; Worker plain‑text vars set in the dashboard get wiped on each deploy.
| Variable | Description |
|---|---|
AI_MODEL | Workers AI reply model. Default @cf/meta/llama-4-scout-17b-16e-instruct. |
AI_FAST_MODEL | Fast classifier model (prompt‑injection scan + persona safety check). Default @cf/meta/llama-3.1-8b-instruct-fast. |
Secrets
Sensitive values, set with wrangler secret put. Grouped by integration.
Core
| Secret | Description |
|---|---|
ENCRYPTION_KEY |
32‑byte hex key for AES‑256‑GCM encryption of stored tokens. Generate with openssl rand -hex 32. |
Google OAuth
| Secret | Description |
|---|---|
GOOGLE_OAUTH_CLIENT_ID | Google OAuth client ID (for sign-in). |
GOOGLE_OAUTH_CLIENT_SECRET | Google OAuth client secret. |
Meta: Facebook / Instagram / WhatsApp
| Secret | Description |
|---|---|
META_APP_ID | Meta app ID: shared for Facebook Login, Instagram, WhatsApp signup. |
META_APP_SECRET | Meta app secret: used for webhook signature verification and token exchange. |
WHATSAPP_ACCESS_TOKEN | System user token for your shared WABA. |
WHATSAPP_VERIFY_TOKEN | Webhook verification token. Generate with openssl rand -hex 16. |
INSTAGRAM_VERIFY_TOKEN | Instagram webhook verification token. Generate with openssl rand -hex 16. |
Discord
| Secret | Description |
|---|---|
DISCORD_PUBLIC_KEY | Discord application public key (Ed25519 signature verification for interactions). |
DISCORD_APPLICATION_ID | Discord application ID (for registering slash commands). |
DISCORD_BOT_TOKEN | Discord bot token (for sending messages and managing interactions). |
Razorpay optional
| Secret | Description |
|---|---|
RAZORPAY_KEY_ID | Razorpay API key ID. |
RAZORPAY_KEY_SECRET | Razorpay API key secret. |
RAZORPAY_WEBHOOK_SECRET | Razorpay webhook secret (for verifying payment notifications at POST /webhook/razorpay). |
Bindings
Cloudflare resource bindings declared in wrangler.toml. The IDs come from the deployment guide.
| Binding | Type | Description |
|---|---|---|
DB | D1 | SQLite database for message logs, payments, audit, credit packs. |
KV | KV | Config store for tenants, accounts, sessions, routing rules, billing state, persona. |
AI | AI | Cloudflare Workers AI for reply generation, prompt‑injection scanning, persona safety classification, and BGE embeddings. |
EMAIL | send_email | Outbound email for forwarding and reverse‑alias replies. |
REPLY_BUFFER | DO | Per‑conversation reply buffer that batches multi‑message bursts within wait_seconds. |
SAFETY_QUEUE | Queue | Persona safety classifier jobs. Producer + consumer bound on the same Worker. |
Cloudflare Queues
Two queues must exist before deploy:
concierge-safety: producer + consumer for the persona safety classifier. The Worker enqueues aSafetyJobon each persona save and consumes the same queue via#[event(queue)].concierge-safety-dlq: dead‑letter queue for safety jobs after 3 retries. Inspect failures here when persona checks stop completing.
wrangler queues create concierge-safety
wrangler queues create concierge-safety-dlq
Local wrangler dev works without queues configured: safety_queue::enqueue logs and falls through, leaving personas in Pending until you deploy against a properly bound environment.
Locale & supported languages
Every tenant has a BCP‑47 locale tag (Tenant.locale) plus an independent currency override. Currently shipped: en-IN (default; Indian‑style number grouping, INR currency) and en-US (Western grouping, USD). Locale is set at signup from the request’s Accept-Language header, falling back to cf-ipcountry, then to en-IN. Admins can change both locale and currency independently from /dashboard/settings.
Adding a new locale is a drop‑in change: place a translated messages.ftl at assets/locales/{tag}/, register the tag in src/i18n.rs::Translator::new and src/locale.rs::Locale::from_request, then rebuild. CLDR data for number / currency formatting ships automatically via icu4x’s compiled_data feature. AI‑generated reply content stays English regardless of the UI locale.
Conversation timing
Three per‑tenant knobs control how the worker treats a customer thread. They live on OnboardingState.conversation (a ConversationConfig) in KV at onboarding:{tenant}; each is Option<u32> and falls back to a constant in src/prompt.rs when unset. Tenants edit them from the Conversation Timing card on /dashboard/settings; PUT /dashboard/settings/conversation validates bounds and the cross‑field invariant before persisting.
| Field | Bounds | Default | Effect |
|---|---|---|---|
idle_gap_mins |
5…1440 mins | 360 (6 h) — DEFAULT_CONVERSATION_IDLE_GAP_MINS |
After this much customer silence, the next inbound starts a fresh conversation: history clears, handoff state is wiped, the persona replies normally. |
handoff_cooldown_mins |
5…1440 mins | 60 — DEFAULT_HANDOFF_COOLDOWN_MINS |
After the model emits [[HANDOFF]], replies switch to the holding‑pattern voice for this long; past it, the worker stays silent. |
max_history_messages |
1…200 turns | 20 — DEFAULT_CONVERSATION_MAX_MESSAGES |
Cap on the number of recent (role, content) turns passed to the reply model on each AI call. |
Cross‑field invariant: idle_gap_mins > handoff_cooldown_mins. The form rejects saves that would let an active handoff be wiped before its cooldown ended.
OAuth redirect URIs
Register these in the respective developer consoles:
- Google:
https://your-domain/auth/callback - Facebook:
https://your-domain/auth/facebook/callbackandhttps://your-domain/instagram/callback
Webhook URLs
Configure these in the respective platforms:
| Platform | URL | Method |
|---|---|---|
https://your-domain/webhook/whatsapp | GET / POST | |
https://your-domain/webhook/instagram | GET / POST | |
| Discord | https://your-domain/discord/interactions | POST |
| Razorpay | https://your-domain/webhook/razorpay | POST |
Cron triggers
A daily cron runs at 0 6 * * * (06:00 UTC) for Instagram token refresh. Configured in the [triggers] section of wrangler.toml.
# Identity
name = "concierge"
main = "worker-shim.mjs"
compatibility_date = "2024-01-01"
# Plain-text variables (deployment-specific values come in via --var at deploy time)
[vars]
ENVIRONMENT = "production"
# Bindings
[[d1_databases]]
binding = "DB"
database_name = "concierge"
database_id = "…"
[[kv_namespaces]]
binding = "KV"
id = "…"
[ai] binding = "AI"
[[send_email]] name = "EMAIL"
[[durable_objects.bindings]]
name = "REPLY_BUFFER"
class_name = "ReplyBufferDO"
# Persona safety classifier
[[queues.producers]]
queue = "concierge-safety"
binding = "SAFETY_QUEUE"
[[queues.consumers]]
queue = "concierge-safety"
max_batch_size = 10
max_batch_timeout = 5
max_retries = 3
dead_letter_queue = "concierge-safety-dlq"
# Daily Instagram token refresh, 06:00 UTC
[triggers]
crons = ["0 6 * * *"]
Rotating ENCRYPTION_KEY invalidates every encrypted Instagram page token in KV: customers will need to reconnect. Webhook verify tokens (WHATSAPP_VERIFY_TOKEN, INSTAGRAM_VERIFY_TOKEN) can be rotated independently; just update them in the Meta Developer Console at the same time.