--- title: Environment Variables | Formbricks Hub description: Runtime configuration for the Hub API, worker, database pool, webhooks, enrichment (embeddings, translation, sentiment, emotions), and observability. --- ## Required | Variable | Used by | Default | Description | | -------------- | ---------- | ----------------------- | ----------------------------------------------------------------------- | | `API_KEY` | API | - | Bearer token required for authenticated `/v1/*` API requests. | | `DATABASE_URL` | API/worker | Local test database URL | PostgreSQL connection string. Set explicitly outside local development. | `hub-worker` requires an explicit `DATABASE_URL`; it does not use the local test default. ## Server | Variable | Default | Description | | -------------------------- | ------- | ------------------------------------------------------------------------------------------------------ | | `PORT` | `8080` | HTTP port used inside the API process/container. | | `PUBLIC_BASE_URL` | - | Public API root advertised in runtime OpenAPI specs when Hub is behind ingress, TLS, or a path prefix. | | `LOG_LEVEL` | `info` | Log level, such as `debug`, `info`, `warn`, or `error`. | | `SHUTDOWN_TIMEOUT_SECONDS` | `30` | Graceful shutdown timeout in seconds. | The Quick Start Docker Compose file also supports `HUB_PORT`, `POSTGRES_PORT`, and `HUB_IMAGE_TAG` as Compose conveniences. They are compose variables, not Hub runtime settings. `PUBLIC_BASE_URL` must be an absolute `http` or `https` URL. It may include a path prefix, such as `https://example.com/hub`, but must not include query strings, fragments, or credentials. Hub serves runtime OpenAPI specs at `/openapi.yaml` and `/openapi.json`. ## Database Pool | Variable | Default | Description | | -------------------------------------- | ------- | ------------------------------------------------ | | `DATABASE_MAX_CONNS` | `25` | Maximum PostgreSQL pool connections. | | `DATABASE_MIN_CONNS` | `0` | Minimum PostgreSQL pool connections. | | `DATABASE_MAX_CONN_LIFETIME_SECONDS` | `3600` | Maximum connection lifetime. | | `DATABASE_MAX_CONN_IDLE_TIME_SECONDS` | `1800` | Maximum idle time before a connection is closed. | | `DATABASE_HEALTH_CHECK_PERIOD_SECONDS` | `60` | Pool health-check interval. | | `DATABASE_CONNECT_TIMEOUT_SECONDS` | `10` | Database connection timeout. | ## River Worker | Variable | Default | Description | | --------------------------------------- | ------- | ---------------------------------------------------------------- | | `RIVER_JOB_TIMEOUT_SECONDS` | `0` | Max job runtime before cancellation; `0` uses the River default. | | `RIVER_RESCUE_STUCK_JOBS_AFTER_SECONDS` | `0` | Stuck-job rescue interval; `0` uses the River default. | | `RIVER_COMPLETED_JOB_RETENTION_SECONDS` | `86400` | Completed job retention period; `-1` disables deletion. | | `RIVER_CLIENT_ID` | - | Optional client identifier for logs and River coordination. | ## Webhooks | Variable | Default | Description | | ------------------------------------ | ----------------------------------------- | --------------------------------------------------- | | `WEBHOOK_DELIVERY_MAX_CONCURRENT` | `100` | Max concurrent webhook delivery workers. | | `WEBHOOK_DELIVERY_MAX_ATTEMPTS` | `3` | Max delivery attempts per webhook job. | | `WEBHOOK_MAX_FAN_OUT_PER_EVENT` | `500` | Max webhook jobs inserted for a single event batch. | | `WEBHOOK_MAX_COUNT` | `500` | Max total webhook endpoints allowed. | | `WEBHOOK_HTTP_TIMEOUT_SECONDS` | `15` | Outbound webhook request timeout. | | `WEBHOOK_ENQUEUE_MAX_RETRIES` | `3` | Retries when enqueueing webhook jobs fails. | | `WEBHOOK_ENQUEUE_INITIAL_BACKOFF_MS` | `100` | Initial enqueue retry backoff in milliseconds. | | `WEBHOOK_ENQUEUE_MAX_BACKOFF_MS` | `2000` | Maximum enqueue retry backoff in milliseconds. | | `WEBHOOK_BLACKLIST` | `localhost,127.0.0.1,::1,169.254.169.254` | Comma-separated hostnames blocked for webhook URLs. | ## Event Publisher | Variable | Default | Description | | --------------------------------------------- | ------- | ------------------------------------- | | `MESSAGE_PUBLISHER_QUEUE_MAX_SIZE` | `16384` | In-memory event channel buffer size. | | `MESSAGE_PUBLISHER_PER_EVENT_TIMEOUT_SECONDS` | `10` | Per-event publish timeout in seconds. | ## Enrichment Hub runs four independent text enrichments — embeddings, translation, sentiment, and emotions — processed asynchronously by `hub-worker`. Each is enabled per type by setting its `*_PROVIDER` and `*_MODEL`; if either is unset, that enrichment is disabled and no jobs are enqueued. All four accept the same providers: `openai`, `google` (Gemini Developer API / Google AI Studio), and `google-gemini` (Gemini Enterprise Agent Platform API). `openai` and `google` authenticate with the enrichment’s `*_PROVIDER_API_KEY`; `google-gemini` uses Google Cloud Application Default Credentials and reads `*_GOOGLE_CLOUD_PROJECT` / `*_GOOGLE_CLOUD_LOCATION`, falling back to the global `GOOGLE_CLOUD_PROJECT` / `GOOGLE_CLOUD_LOCATION` (set `GOOGLE_APPLICATION_CREDENTIALS` to a service-account key path when running outside Google Cloud). A custom `*_BASE_URL` is honored only with `openai`. ### Embeddings | Variable | Default | Description | | --------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `EMBEDDING_PROVIDER` | - | Embedding provider (`openai`, `google`, or `google-gemini`; legacy `google-vertex` maps to `google-gemini`). Set with `EMBEDDING_MODEL` to enable. | | `EMBEDDING_PROVIDER_API_KEY` | - | API key for `openai` / `google`. | | `EMBEDDING_MODEL` | - | Embedding model name. | | `EMBEDDING_BASE_URL` | - | OpenAI-compatible embeddings root URL. Use with `EMBEDDING_PROVIDER=openai` to target a self-hosted endpoint. See [Self-Hosted Embeddings](/guides/hub-self-hosted-embeddings/index.md). | | `EMBEDDING_MAX_CONCURRENT` | `5` | Max concurrent embedding jobs. | | `EMBEDDING_MAX_ATTEMPTS` | `3` | Max embedding attempts. | | `EMBEDDING_NORMALIZE` | `false` | Whether to normalize embeddings before storage. | | `EMBEDDING_GOOGLE_CLOUD_PROJECT` | - | Google Cloud project (for `google-gemini`). | | `EMBEDDING_GOOGLE_CLOUD_LOCATION` | - | Google Cloud location (for `google-gemini`). | ### Translation | Variable | Default | Description | | ----------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `TRANSLATION_PROVIDER` | - | Translation provider (`openai`, `google`, or `google-gemini`; legacy `google-vertex` maps to `google-gemini`). Set with `TRANSLATION_MODEL` to enable. | | `TRANSLATION_PROVIDER_API_KEY` | - | API key for `openai` / `google`. | | `TRANSLATION_MODEL` | - | Translation model name. | | `TRANSLATION_BASE_URL` | - | OpenAI-compatible base URL; honored only with `openai`. | | `TRANSLATION_DEFAULT_LANGUAGE` | - | Fallback target language (BCP-47, e.g. `en-US`) for tenants with no `target_language`. Empty makes translation per-tenant opt-in. | | `TRANSLATION_MAX_CONCURRENT` | `5` | Max concurrent translation jobs. | | `TRANSLATION_MAX_ATTEMPTS` | `3` | Max translation attempts. | | `TRANSLATION_GOOGLE_CLOUD_PROJECT` | - | Google Cloud project (for `google-gemini`). | | `TRANSLATION_GOOGLE_CLOUD_LOCATION` | - | Google Cloud location (for `google-gemini`). | Each record is translated into its tenant’s `target_language` (set via the tenant settings API); `TRANSLATION_DEFAULT_LANGUAGE` supplies the target for tenants that have not set one. ### Sentiment | Variable | Default | Description | | --------------------------------- | ------- | -------------------------------------------------------------------------------------------------- | | `SENTIMENT_PROVIDER` | - | Sentiment provider (`openai`, `google`, or `google-gemini`). Set with `SENTIMENT_MODEL` to enable. | | `SENTIMENT_PROVIDER_API_KEY` | - | API key for `openai` / `google`. | | `SENTIMENT_MODEL` | - | Sentiment model name. | | `SENTIMENT_BASE_URL` | - | OpenAI-compatible base URL; honored only with `openai`. | | `SENTIMENT_MAX_CONCURRENT` | `5` | Max concurrent sentiment jobs. | | `SENTIMENT_MAX_ATTEMPTS` | `3` | Max sentiment attempts. | | `SENTIMENT_GOOGLE_CLOUD_PROJECT` | - | Google Cloud project (for `google-gemini`). | | `SENTIMENT_GOOGLE_CLOUD_LOCATION` | - | Google Cloud location (for `google-gemini`). | ### Emotions | Variable | Default | Description | | -------------------------------- | ------- | ----------------------------------------------------------------------------------------------- | | `EMOTIONS_PROVIDER` | - | Emotion provider (`openai`, `google`, or `google-gemini`). Set with `EMOTIONS_MODEL` to enable. | | `EMOTIONS_PROVIDER_API_KEY` | - | API key for `openai` / `google`. | | `EMOTIONS_MODEL` | - | Emotion model name. | | `EMOTIONS_BASE_URL` | - | OpenAI-compatible base URL; honored only with `openai`. | | `EMOTIONS_MAX_CONCURRENT` | `5` | Max concurrent emotion jobs. | | `EMOTIONS_MAX_ATTEMPTS` | `3` | Max emotion attempts. | | `EMOTIONS_GOOGLE_CLOUD_PROJECT` | - | Google Cloud project (for `google-gemini`). | | `EMOTIONS_GOOGLE_CLOUD_LOCATION` | - | Google Cloud location (for `google-gemini`). | Sentiment and emotions additionally honor per-directory switches (`sentiment_enabled`, `emotions_enabled`) set through the [tenant settings API](/core-concepts/tenant-settings/index.md); both default to on when the enrichment is configured. ## Taxonomy [Taxonomy](/core-concepts/taxonomy/index.md) clusters embedded feedback into a tree of topics via a standalone service. It requires embeddings (see [Embeddings](#embeddings)) to be configured. | Variable | Default | Description | | ------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------- | | `TAXONOMY_SERVICE_URL` | - | Absolute `http(s)` URL Hub calls to run clustering. Unset disables taxonomy. | | `TAXONOMY_SERVICE_TOKEN` | - | Bearer token Hub sends to the taxonomy service. | | `HUB_INTERNAL_API_TOKEN` | - | Token the taxonomy service presents to call Hub’s internal callback endpoints. Required to enable the internal routes. | | `TAXONOMY_MIN_EMBEDDED_RECORDS` | `20` | Minimum embedded records a scope needs before a run may start. | ## Tenant Settings Cache The translation, sentiment, and emotion enqueue providers each resolve a per-tenant setting (`target_language`, `sentiment_enabled`, `emotions_enabled`) on every feedback event. They share a small in-process LRU cache over tenant settings so that resolution does not hit the database on each event; a settings write evicts the tenant’s entry so a change is visible immediately. | Variable | Default | Description | | ----------------------------------- | ------- | ------------------------------------------------------------------------ | | `TENANT_SETTINGS_CACHE_SIZE` | `2048` | Maximum number of tenants cached (LRU). Set to `0` to disable the cache. | | `TENANT_SETTINGS_CACHE_TTL_SECONDS` | `60` | How long a cached settings entry stays fresh, in seconds. | ## Observability | Variable | Default | Description | | ----------------------- | ------- | ------------------------------- | | `OTEL_METRICS_EXPORTER` | - | OpenTelemetry metrics exporter. | | `OTEL_TRACES_EXPORTER` | - | OpenTelemetry traces exporter. | With `OTEL_METRICS_EXPORTER` set, Hub emits the metrics listed in the [Metrics](/reference/metrics/index.md) reference.