Skip to main content
Configuration lives in .env. The full list of variables with their defaults is in Environment variables; this page is about the choices behind them.

Two database connections

FaPost uses one PostgreSQL server with two logical scopes. Landlord holds platform-level records — the tenant list, the webhook routing registry and the platform’s own records. Tenant holds everything belonging to an organisation, in its own schema. You configure one connection. The tenant schema is selected at runtime from it, which is why there is no second set of DB_* variables and why the database user needs the CREATE privilege: schemas are created as tenants are. Landlord reads the same DB_* values. The LANDLORD_DB_* variables (host, port, database, username, password) are advanced and not supported for now: with a separate landlord database, root tables that the application reads through the default connection (cache, jobs, failed jobs, Telescope) end up where nothing reads them. Leave them unset. Tenant schemas are created on the tenant connection (TENANT_DB_CONNECTION, pgsql by default, configured by the DB_* values), whatever the landlord variables say.

Redis is not optional

Redis carries four things, and losing any of them has a distinct symptom: The registry is the one that surprises people. It exists so that resolving an incoming webhook never touches the landlord database — the hot path stays a Redis lookup. That makes Redis part of the delivery path, not just an accelerator.
REDIS_PREFIX must match between the application and the webhook gateway if you run one. A mismatch is silent: the gateway reads an empty registry, decides it knows nothing, and forwards every delivery to PHP. Everything works, slower, for no visible reason.

Webhooks and the public address

Two variables decide where providers deliver. WEBHOOK_BASE_URL is the address the application builds callback URLs from. It must be publicly reachable over HTTPS — providers will not deliver to anything else. WEBHOOK_INGRESS_DRIVER chooses what handles them: laravel for the application itself, gateway for the Go service.
The driver applies to newly registered channels only. Existing channels keep the URL their provider already stored, and the Laravel route stays live regardless — so switching is not a cutover. Move the rest with php artisan ops:ingress-migrate --apply.

Outbound calls from flows

The call node refuses to connect to private, loopback, link-local, reserved and cloud-metadata addresses, on every installation and with no switch to turn it off. A refused call takes the node’s error handle with error_code = egress_denied. See the call node. For local development, FLOW_EGRESS_ALLOW=127.0.0.1/32 lets a flow reach a mock server on the same machine. 0.0.0.0/0 would switch the guard off in practice, and Core logs a warning when it sees it. Calls ignore the HTTP_PROXY and HTTPS_PROXY environment variables: a proxy that resolves the name itself would bypass the check. Behind FLOW_EGRESS_PROXY Core still refuses what it resolves, but it cannot pin the connection, so the proxy must refuse private ranges itself (for example Smokescreen). Names are resolved with DNS first and then with the system resolver, so /etc/hosts entries and container extra_hosts work; whatever they resolve to is still checked, so a non-public result needs FLOW_EGRESS_ALLOW (for example FLOW_EGRESS_ALLOW=127.0.0.1/32 for a mock on localhost). Network policy on the host (firewall rules, a restricted container network, IMDSv2 on cloud machines) is a worthwhile second layer, since it also covers code the guard does not.

What must change for production

The shipped .env.example is a development file. Four values are unsafe as they stand: Set a REDIS_PASSWORD on anything but a local machine.

Languages

APP_LOCALE and APP_FALLBACK_LOCALE are the admin interface language. What an assistant says to a contact is decided elsewhere entirely — tenant settings, assistant settings, and the flow itself. Changing these variables does not change a single word a contact receives.

Applying changes

Configuration is read at boot. After editing .env:
Then restart Horizon — long-running workers hold the configuration they started with, and a worker that has not been restarted is still using the old values however many times you clear the cache.