Environment Variables¶
Applies to: All subscriptions
Purpose¶
Provide the complete reference for host-level configuration: what each setting controls, what happens if it is wrong, and which subsystem stops working. This is the page an administrator opens when something is misconfigured and the log is unhelpful.
Audience¶
System administrators and implementation engineers. Integration developers, for the authentication and write-back groups.
Prerequisites¶
- Installation Guide complete
- File-system access to the application host
Reference¶
Settings live in an environment file read by the API and worker at startup. Every change requires a restart of the affected process.
Treat this file as a credential store: restrict its permissions to the service account and administrators, keep it out of source control, and own the rotation of what it holds.
Core application¶
| Setting | Purpose | Note |
|---|---|---|
NODE_ENV |
Runtime mode | production in production. Development mode changes error verbosity and logging |
PORT |
API listening port | 5003 in the reference deployment. A built-in fallback exists and must not be relied on — if the proxy and service disagree, the symptom is HTTP 502 with a silent application log |
APP_NAME |
Display name in logs | Cosmetic |
APP_BASE_URL |
The application's own public URL | Used to build links. Wrong here means emails and documents point at the wrong environment |
CORS_ORIGINS |
Browser origins permitted to call the API | Only needed when the front end is on a different origin. Never a wildcard |
LOG_LEVEL |
Log verbosity | info in production. debug is for diagnosis and generates a lot of volume |
LOG_PRETTY |
Human-readable log formatting | Off in production, where logs are machine-collected |
Secret resolution (Azure Key Vault)¶
| Setting | Purpose | Note |
|---|---|---|
KEY_VAULT_URL |
Switches where the Tier 1–2 secrets in this file come from — Azure Key Vault instead of the values set directly below (DB_PASSWORD, JWT_SECRET, AGENT_GATEWAY_SECRET, GSP credentials, and others; the exact list is gst-backend/services/secrets/mapping.js) |
Absent (the default everywhere today): every setting in this file is read exactly as written below — nothing changes. Set: at startup, each mapped secret is fetched from the named vault and overrides the value below under the same variable name, so no other config module needs to change. If a Tier 1 secret (DB_PASSWORD, JWT_SECRET, AGENT_GATEWAY_SECRET) fails to resolve — or resolves to an unmigrated placeholder — startup fails loudly instead of continuing with a broken or wrong credential. A non-Tier 1 secret still holding its placeholder is left unset in the environment (a warning is logged), never set to the placeholder text — so REDIS_PASSWORD stays unset until a real value is in the vault |
As of this writing, KEY_VAULT_URL is not set on any deployed environment — every server still runs entirely off the values in this file, unchanged. The corresponding Key Vaults (complifly-kv-dev/qa/prod) are populated with real values ahead of time, but nothing reads from them yet; that cutover is a separate, later step per environment.
Database¶
| Setting | Purpose | Failure if wrong |
|---|---|---|
DB_SERVER |
Host or address, with instance name if applicable | API refuses to start |
DB_PORT |
Database port | Connection timeout |
DB_DATABASE |
Database name | Startup failure, often misread as a credential problem |
DB_USER / DB_PASSWORD |
Application login | Authentication failure at startup |
DB_ENCRYPT |
Encrypt the connection | Should be enabled. Disabling puts credentials and data in clear text |
DB_TRUST_SERVER_CERT |
Whether to accept the server certificate without validation | A deliberate, recorded decision. Acceptable only inside a controlled network |
DB_POOL_MIN / DB_POOL_MAX |
Connection pool bounds | Too low starves under load; too high exhausts database connections. Tune only against measurement |
DB_CONNECTION_TIMEOUT_MS |
Connection establishment timeout | Too low produces spurious startup failures on a busy instance |
DB_REQUEST_TIMEOUT_MS |
Per-query timeout | Too low fails long reports; too high hides a runaway query |
DB_POOL_IDLE_TIMEOUT_MS |
Idle connection lifetime | Rarely needs changing |
DB_APP_NAME |
Application name in database sessions | Useful for identifying Complifly connections when diagnosing |
Authentication and identity¶
| Setting | Purpose | Failure if wrong |
|---|---|---|
JWT_SECRET |
Signs and verifies session tokens | Users cannot sign in. Changing it invalidates every existing session — a deliberate act, not a routine one. Must be long, random, and different per environment |
HOSTNAME |
Identifies which node wrote a given JWT key-rotation audit count, when more than one server is running | Set automatically by the OS/shell on every Linux host — never set this by hand. Absent (unlikely): the audit event records the node as unknown, which still works but makes a multi-node rotation harder to read |
JWT_KEY_ID, JWT_SECRET_PREVIOUS, JWT_PREVIOUS_KEY_ID, JWT_PREVIOUS_VALID_UNTIL |
Key rotation for JWT_SECRET without forcing every user to sign in again. New tokens carry JWT_KEY_ID in their header and are signed with JWT_SECRET. A token signed with the previous key (JWT_SECRET_PREVIOUS, identified by JWT_PREVIOUS_KEY_ID) is still accepted, but only until JWT_PREVIOUS_VALID_UNTIL (an ISO timestamp) passes — after that it is rejected even if the value is still configured |
All four absent (the default): behaves exactly as JWT_SECRET alone always has. Set during a rotation: both keys are valid for a bounded window (recommended 48 hours — covers the 8-hour token lifetime plus a rolling restart, restart delays and clock skew). Each successful validation is logged as an aggregated count (not per-request) under JWT_KEY_CURRENT_VERIFIED / JWT_KEY_PREVIOUS_VERIFIED in the audit trail, with no token claims or key values — this is how an operator confirms traffic has moved off the previous key before retiring it |
AZURE_TENANT_ID |
Your own identity tenant — used for user sign-in, and to recognise ERP app registrations that live in it. It is not the tenant an ERP token must come from; that is recorded per connection | User sign-in fails |
AZURE_CLIENT_ID |
Application registration identifier for user sign-in | Sign-in token validation fails |
AZURE_API_AUDIENCE |
Accepted token audience values for machine tokens | Valid tokens rejected. Both the application identifier URI and the bare identifier may be needed, depending on token version |
AZURE_REQUIRED_ROLE |
Application role a machine token must carry | Authenticated calls rejected as unauthorised |
AZURE_API_APP_ID |
The API application registration customers grant admin consent to. That registration must be multi-tenant, or no customer directory can consent | Consent links cannot be generated. The administration screen says so rather than failing later |
AGENT_GATEWAY_SECRET |
Shared secret between the backend and whatever terminates client certificates for the Complifly Agent (nginx in production, scripts/dev-mtls/local-mtls-gateway.js in development). The terminator sends it in X-Gateway-Secret; the backend ignores the client-certificate headers on any request without it. No default, and it must not have one — with it unset the backend treats no request as certificate-verified, so every agent call fails with AGENT_CERT_REQUIRED |
Agent calls rejected with AGENT_CERT_REQUIRED even with a valid certificate. If the value differs between the backend and the terminator the symptom is identical, so check both. Anyone who learns it can impersonate any agent whose certificate serial they know — treat it like JWT_SECRET |
ERP_CONSENT_REDIRECT_URI |
Where Microsoft Entra returns a customer's administrator after consent | Consent fails at the customer's end with an AADSTS error you never see |
GOOGLE_CLIENT_ID |
Social sign-in, where enabled | Social sign-in fails |
OTP_EXPIRY_MINUTES |
One-time code lifetime | Too short frustrates users; too long weakens the control |
OTP_MAX_ATTEMPTS |
Attempts before lockout | Balance usability against brute-force resistance |
OTP_RESEND_COOLDOWN_SECONDS |
Minimum interval between code requests | Prevents message flooding |
SESSION_MAX_CONCURRENT |
How many devices a user may be signed in on at once. Default 1 — a new sign-in beyond the limit signs the oldest device out automatically | Set higher (e.g. 2) for a workforce that legitimately works from a desktop and a phone at once. Setting it very high approaches the old (pre-session-registry) behaviour of effectively unlimited concurrent sign-ins — see User Authentication |
SESSION_TTL_HOURS |
How long a session token is valid for before re-authentication is required | Longer eases usability; shorter reduces the window a lost or stolen token remains usable |
SESSION_TOUCH_THROTTLE_MS |
How often the Active Sessions "last activity" timestamp is written, per session | A performance knob only — never affects whether a session is valid; lowering it writes more often but reflects activity slightly more precisely |
PAN_ENCRYPTION_KEY |
Encrypts stored identity numbers | Losing this makes existing encrypted values unreadable. Back it up separately from the database, or a restore recovers rows nobody can read |
PAN_ENCRYPTION_KEY deserves particular care. It is the one setting whose loss is not recoverable from a database backup, because the backup contains only the ciphertext.
ERP_CONSENT_REDIRECT_URI must match Entra character for character. This value is registered as a redirect URI on the API application in Microsoft Entra. If the two disagree by so much as a trailing slash, consent fails inside the customer's directory, where the only diagnostic is an AADSTS code on their screen.
It is therefore set explicitly rather than derived from the domain map: a hostname change must be a deliberate, paired edit — this variable and the Entra registration, in the same change. Moving the public address without updating Entra breaks onboarding for every new customer while leaving existing ones working, so nothing alerts you.
An ERP token is not validated against AZURE_TENANT_ID. Each connection records the customer's own directory, and the token is checked against that. This is the setting most often misread when an integration is refused — widening or changing it does not admit a customer's ERP, and never should.
GSP providers¶
| Setting group | Purpose |
|---|---|
PROVIDER |
The default provider for this deployment |
WEBTEL_* |
Webtel credentials and per-operation endpoint URLs |
MI_* |
Masters India credentials, base URLs and timeout |
EINV_USER / EINV_PASS |
e-Invoice service credentials |
EF_USER / EF_PASS |
e-Way Bill service credentials |
GSP_CONFIG_CACHE_TTL_MS |
How long provider configuration is cached |
Provider endpoint URLs are supplied by the provider and differ between sandbox and production. Mixing them is the mechanism by which a test document reaches the live government system.
See GSP Configuration for assignment and testing.
Mail¶
All outbound mail — password reset, one-time codes, e-Way Bill expiry alerts, ERP sync failures, platform alerts — goes through Azure Communication Services. There is no SMTP path: a prior raw-SMTP implementation (SMTP_HOST/SMTP_USER/SMTP_PASS/etc., authenticating as a Microsoft 365 mailbox) was retired after Microsoft disabled tenant-wide SMTP basic auth, which broke it outright regardless of the password's correctness. Azure Communication Services authenticates with an access key in a connection string instead of a mailbox login, so that class of failure does not apply to it.
| Setting | Purpose | Failure if wrong |
|---|---|---|
AZURE_COMMUNICATION_CONNECTION_STRING |
Endpoint + access key for the Azure Communication Services resource that sends all mail | No notifications, no password reset, no one-time codes. Users are locked out with no visible cause |
AZURE_EMAIL_SENDER_ADDRESS |
Sender address, e.g. DoNotReply@complifly.in. Must be a MailFrom address verified under a domain connected to the Communication Services resource (Azure Portal → the resource → Email → Domains) |
Send fails — the address is not authorized to send from that resource |
Mail failures are the most under-appreciated configuration fault in the product. They do not surface at startup, they produce no error in the interface, and the symptom reaching support is "a user cannot log in".
Redis — cache, rate limiting, and (reserved) the job queue¶
One Redis instance serves three separate purposes, kept apart by database index — never share an index across them, or a job-queue key and a cached value could collide.
| Setting | Purpose | Note |
|---|---|---|
REDIS_HOST / REDIS_PORT / REDIS_PASSWORD |
Connection, shared across all three uses below | Wrong here affects caching and rate limiting immediately — not just the queue |
REDIS_CACHE_DB |
Database index for services/cache.js (feature flags, theme, session policy, GSP config, master-data caching) |
Default 0. Fails open: any Redis error is treated as a cache miss and the app reads SQL Server directly — degraded performance, never a 500 |
REDIS_CACHE_OP_TIMEOUT_MS |
How long a single cache read/write may take before it's treated as failed | Default 250. Too high delays the fail-open fallback under a real outage |
MASTER_CACHE_TTL_MS |
How long a whole small master-data list (states, countries, UQC, etc.) is cached | Default 24 hours. Explains why a bulk master-data import doesn't appear immediately — see the import's own cache-invalidation step |
MASTER_QUERY_CACHE_TTL_MS |
How long one HSN/GST-rate search result is cached | Default 12 hours — shorter than the whole-table TTL because these two tables are large and/or searched, not held whole in memory |
REDIS_RATELIMIT_DB |
Database index for authLimiter/apiLimiter in server.js |
Default 2. This is what makes the limit genuinely shared across every app server — see Rate Limits and Errors. Fails open the same way as the cache: a Redis error lets the request through unlimited rather than returning an error page |
JOBS_USE_QUEUE |
Whether background work is queue-backed (BullMQ, database index 1) |
Off in production today. When enabled, Redis becomes a hard dependency for job processing specifically — a queue failure here is a silent one: jobs enqueue and are never consumed, and the interface looks entirely healthy |
WORKER_CONCURRENCY |
Parallel jobs per worker, once the queue is enabled | Higher throughput, higher memory. Raise only against measurement |
FILE_UPLOAD_DIR |
Where uploaded files are written | Must be writable by the service account and included in backup planning |
MAX_FILE_SIZE_MB |
Largest accepted upload | Must align with the proxy's body limit, or the proxy rejects first and the message is unhelpful |
IRN submission¶
| Setting | Purpose |
|---|---|
IRN_SUBMIT_MAX_ATTEMPTS |
Retry attempts against the provider |
IRN_SUBMIT_BASE_DELAY_MS |
Base delay for backoff between attempts |
INGEST_IN_FLIGHT_TIMEOUT_MS |
How long an in-flight ingest may remain unresolved |
Retries protect against transient network faults, not against rejections. A rejected document is rejected deterministically; retrying it consumes billed calls to no purpose.
e-Way Bill¶
| Setting | Purpose |
|---|---|
EWB_EXPIRY_ALERTS_ENABLED |
Whether approaching-expiry alerts are raised |
EWB_EXPIRY_HORIZON_HOURS |
How far ahead to look. Set to the customer's real dispatch lead time |
EWB_PRINT_HORIZON_MONTHS |
How far back printed bills are retrievable |
EWB_GENERATION_GUARD |
ENFORCE (the default) or OFF. While enforced, only one generation runs per document at a time on a server, and a document that already has a live e-way bill is refused before any provider call. OFF restores the previous behaviour |
EWB_SUBMIT_MAX_ATTEMPTS |
Total attempts for a request that provably never reached the provider (a refused connection, an unresolvable name, HTTP 429 or 503). Default 3; 1 disables retry |
EWB_SUBMIT_BASE_DELAY_MS |
First backoff step, doubled each attempt with jitter. Default 1500 |
EWB_PERSIST_MAX_ATTEMPTS |
Attempts to record an issued e-way bill after a transient database fault (a deadlock, a timeout). Default 3 |
What is and is not retried. A request that may have reached NIC (a timeout, a reset, an HTTP 500, 502 or 504, or an answer Complifly could not read) is never resubmitted automatically: NIC may already have issued the bill, and a repeat would be refused as a duplicate and recorded as a failure against a document that has a live bill. Such a request is recorded as in doubt, with the code EWB_IN_DOUBT. See e-Way Bill failures.
Housekeeping scheduling and authentication throttling¶
| Setting | Purpose | Note |
|---|---|---|
HOUSEKEEPING_RUN_ON_STARTUP |
Run every enabled retention/archive job once shortly after the server starts | Default true. Safe on every restart: a job that already ran today exits as "already claimed" |
HOUSEKEEPING_STARTUP_DELAY_MS |
Wait before that first run | Default 60000 (60 s), so start-up is not slowed |
HOUSEKEEPING_RETRY_MAX |
How many times a failed pass is retried | Default 3. 0 disables retry |
HOUSEKEEPING_RETRY_DELAY_MS |
First retry delay; each further retry doubles it | Default 900000 (15 min), then 30 min, 60 min |
AUTH_RATE_LIMIT_ENABLED |
Throttle repeated failed requests to /api/auth |
Default true. Setting false disables brute-force protection — for development only; the Observability Security panel shows when it is off |
AUTH_RATE_LIMIT_MAX |
Failed attempts allowed per client address per 15 minutes | Default 20. Successful sign-ins do not count |
A job runs only when both its environment switch (SESSION_REAPER_ENABLED, QUEUE_RETENTION_ENABLED, …) is on and its row in the retention policy has enabled = 1. See Housekeeping.
Attention Center checks¶
All of these are off or at their default when absent. With the first one off,
the Attention API has no findings and reports sweepHealthy: false — users are
told the checks are not running rather than shown an all-clear.
| Setting | Purpose | Default and what happens when absent |
|---|---|---|
COMPLIANCE_SWEEP_ENABLED |
Run the scheduled check that opens, refreshes and closes Attention Center findings for every registration | Off. No findings are created or closed. Set true on every application server; they elect one runner per interval between themselves, so it is safe on all of them |
COMPLIANCE_SWEEP_INTERVAL_MS |
How often the check runs | 900000 (15 minutes). Values below 60000 are ignored. A finding is reported as stale after two intervals without a completed pass |
COMPLIANCE_HOOKS_ENABLED |
Re-check a document's findings within seconds of it changing (IRN generated, e-way bill extended), instead of waiting for the next scheduled pass | Off. Findings still close — at the next scheduled pass |
FINDING_RETENTION_ENABLED |
Schedule the job that purges closed findings past their retention window | Off. Closed findings are kept indefinitely. Also needs the two compliance_finding.* retention policy rows enabled — see Housekeeping |
FINDING_RETENTION_INTERVAL_MS |
How often that job runs | 86400000 (daily) |
ATTENTION_NOTIFICATIONS_ENABLED |
Run the job that sends the emails the notification settings ask for (assigned, escalated, reopened, daily and weekly summaries). This job emails real people. It is the server-wide stop: off, no company is emailed. Inside it, each company also needs the NOTIFICATION_DISPATCH_ENABLED flag on (default off, Feature Flags), which only a Complifly Super Admin can set |
Off. Notification settings can be saved but no email goes out, for any company; the in-app bell is unaffected. Also needs Azure email configured (AZURE_COMMUNICATION_CONNECTION_STRING and AZURE_EMAIL_SENDER_ADDRESS); without them it logs that it did not start. Safe on every application server: a ledger row written before each send stops two servers sending the same email |
ATTENTION_NOTIFICATIONS_INTERVAL_MS |
How often it looks for something to send | 900000 (15 minutes). Values below 60000 are ignored. It does not run at start-up, so a restart loop cannot replay the last two days |
The checks evaluate the rules installed in the database (GET /api/attention/rules
lists them). A rule is installed by its migration, which Complifly runs during
rollout.
Changing any of these needs a restart of the application server. Setting them requires access to the server's environment; a Complifly administrator role inside the application cannot change them.
Observability (MVP, non-Azure)¶
Off by default — flip the two _ENABLED switches to turn this on, no deploy
required. See Logs and Observability
for what each piece covers and eng-portal ADR-0020 for what is deliberately
deferred to Phase 2 (Azure Log Analytics, Application Insights, distributed
tracing).
| Setting | Purpose | Note |
|---|---|---|
PLATFORM_HEALTH_SWEEP_ENABLED |
Master switch for the 5-minute health sweep | Sends real alert email when a P1/P2 condition is detected |
PLATFORM_ALERT_RECIPIENTS |
Comma-separated email addresses for P1/P2 alerts | Required for the sweep to actually notify anyone — a missing value means breaches are detected but silent |
PLATFORM_ALERT_RECIPIENTS_P1 / _P2 |
Severity-specific recipient overrides (Phase 1.1) | Each falls back to PLATFORM_ALERT_RECIPIENTS if unset — fully optional, backward compatible. Read dynamically (process.env[\PLATFORM_ALERT_RECIPIENTS_${severity}`]`), so this row is hand-maintained — the automated env-var completeness gate cannot see a dynamically-constructed key |
PLATFORM_EVENT_LOG_RETENTION_ENABLED |
Master switch for the daily platform_event_log purge job |
|
PLATFORM_EVENT_LOG_RETENTION_DAYS |
Rows older than this are deleted | Default 90 |
PLATFORM_EVENT_LOG_RETENTION_INTERVAL_MS |
How often the purge job runs | Default daily |
PLATFORM_EVENT_LOG_PURGE_BATCH |
Rows deleted per batch | Bounded to avoid a long-held table lock on a busy table |
PLATFORM_ALERT_MIN_SAMPLE_IRN / _EWB / _UPLOAD |
Minimum attempts in the alert window before the IRN, e-Way Bill or upload failure-rate alert may fire | Default 5 each. Below it, a high percentage (1 failure out of 1) is ignored. Customer-data validation rejections are excluded from the IRN rate regardless |
LOGIN_FAILURE_ALERT_THRESHOLD |
Failed sign-ins in 15 minutes (all accounts) that raise the LOGIN_FAILURE_SPIKE P2 alert |
Default 25 |
LOGIN_FAILURE_ALERT_PER_USER_THRESHOLD |
Failed sign-ins in 15 minutes against one account that raise the same alert | Default 10 |
SECURITY_REPORT_ENABLED |
Whether the daily npm audit snapshot (Security Health widget) runs |
Read-only — visibility only, not the full Vulnerability Management program. A snapshot that could not run is shown as unknown, never as zero |
POLICY_GATE_EVENTS_ENABLED |
Whether a policy or tenant-lifecycle gate that let a request through without applying its control writes a POLICY_GATE_DEGRADED platform event |
Default on. The count and the log line are unaffected by this switch. See Integration policies |
POLICY_GATE_EVENT_THROTTLE_MS |
Minimum time between events for the same gate and reason | Default 900000 (15 minutes), so a database outage is one row with a count, not a flood. 0 records every occurrence |
COMMERCIAL_CAPACITY_ALERTS_ENABLED |
Master switch for the scheduled capacity-alert run: it notices when a customer crosses 70, 85 or 100 % of a plan limit (or goes over), records each crossing once in that customer's commercial history, and emails the support matrix's commercial contact one digest of what is new | Default off. Records and reports only: nothing reads it as a gate and nothing is ever blocked. Until the business supplies a commercial contact (config/support/supportMatrix.js), crossings are recorded and nobody is emailed; the support-readiness check says so. A Super Admin can also run one on request with POST /api/admin/commercial/capacity-alerts/run (a dry run unless apply is true) |
COMMERCIAL_CAPACITY_ALERTS_INTERVAL_MS |
How often the scheduled capacity-alert run happens | Default 3600000 (an hour). Never faster than 300000 (five minutes); a smaller or unreadable value is the default. Only used when the setting above is on |
COMMERCIAL_PROMOTIONS_ENABLED |
Whether a Complifly Super Admin can apply a voucher (a free term, a longer trial or an optional module for a limited time) to a customer | Default off: applying is refused with PROMOTIONS_DISABLED and nothing is written. Only the string true turns it on. It switches off applying only: reading a customer's vouchers, taking back a mistake, and the rule that a voucher trial needs the customer's recorded acceptance before it becomes a paid term all work either way |
COMMERCIAL_PROMOTION_SWEEP_ENABLED |
Master switch for the scheduled voucher expiry follow-up: it finds a voucher-funded free period that has ended without being converted to a paid term, records it once in the customer's commercial history, and emails the support matrix's commercial contact one digest of the ones they have not yet been told about (it is retried until it has been sent) | Default off. The flag on the Commercial tab does not depend on it. It records and reports only: the customer keeps working and nothing statutory (IRN, e-way bill, cancellation) is ever blocked. Without a commercial contact in the support matrix it records and emails nobody, and the first digest after one is supplied names every expiry still unconverted |
COMMERCIAL_PROMOTION_SWEEP_INTERVAL_MS |
How often the scheduled voucher expiry follow-up runs | Default 3600000 (an hour). Never faster than 300000 (five minutes); a smaller or unreadable value is the default. Only used when the setting above is on |
SENTRY_TRACES_SAMPLE_RATE |
Sentry performance-transaction sample rate | Defaults to 0 (errors only) — deliberately, to avoid a second tracing pipeline before OpenTelemetry's own destination is decided |
ERP write-back¶
Off by default. When enabled, these govern delivery behaviour:
| Setting | Purpose |
|---|---|
ERP_WRITEBACK_ENABLED |
Master switch for the deployment. Enforced at emission — with this off, no outbound event is stored for anyone, and enabling it does not backfill history |
ERP_WRITEBACK_BATCH_SIZE |
Events dispatched per cycle |
ERP_WRITEBACK_DISPATCH_INTERVAL_MS |
How often dispatch runs |
ERP_WRITEBACK_LEASE_SEC |
How long a worker holds a claim on an event |
ERP_WRITEBACK_CIRCUIT_THRESHOLD |
Consecutive failures before the circuit opens |
ERP_WRITEBACK_CIRCUIT_COOLDOWN_SEC / ..._CAP_SEC |
Cooldown after opening, and its ceiling |
ERP_WRITEBACK_RETENTION_ENABLED / ..._DAYS |
How long delivered events are retained |
ERP_WRITEBACK_DEAD_RETENTION_DAYS |
How long dead-lettered events are retained. Must exceed your realistic reaction time, or evidence is purged before anyone investigates |
ERP_WRITEBACK_RAW_PAYLOAD_DAYS |
Retention of raw payloads |
Three things must all be true before a single event is recorded. This variable is only the first of them, and the other two are not environment settings at all:
ERP_WRITEBACK_ENABLEDfor the deployment (here).- The
ERP_WRITEBACK_ENABLEDfeature flag on the specific GSTIN — see Feature Flags. - An active subscription for that ERP connection and GSTIN — see Write-back API.
All three are enforced at emission, so a gap in any one of them means the event is never written — not queued, not skipped, not delayed. Nothing errors on either side. If an ERP reports that it receives no events while everything "looks enabled", check all three before investigating delivery.
| ERP_WRITEBACK_ALERTS_ENABLED / ..._SUPPORT_EMAIL | Failure alerting and its recipient |
| ERP_WRITEBACK_ALLOW_INSECURE_URL | Permits a non-HTTPS endpoint. Never enable in production |
| ERP_WRITEBACK_EMIT_ONLY | Emit events without dispatching, for a pull-based consumer |
| ERP_WRITEBACK_RECONCILE_ENABLED / ..._INTERVAL_MS / ..._LOOKBACK_DAYS / ..._CAP | Reconciliation sweep for events that should have been emitted |
Verification caching¶
| Setting | Purpose |
|---|---|
VERIFY_CACHE_ENABLED |
Whether verification results are cached rather than re-fetched |
VERIFY_CACHE_TTL_GSTIN_DAYS |
Staleness window for registration status |
VERIFY_CACHE_TTL_PAN_DAYS |
Staleness window for identity verification |
VERIFY_CACHE_TTL_UDYAM_DAYS |
Staleness window for enterprise registration |
These directly control the trade-off between provider cost and data freshness. A counterparty's registration status can change after it was cached — shorten the window where that risk matters, lengthen it where it does not.
Other¶
| Setting | Purpose |
|---|---|
FEATURE_CACHE_TTL_MS |
How long a registration's resolved feature flags are cached (default 60 seconds). A change made through Operational Controls clears the cache at once; this is the upper bound if that clearing fails |
FEATURE_DEFINITION_CACHE_TTL_MS |
How long each server keeps the catalogue of operational controls in memory (default 5 minutes). The catalogue changes only when a release's database migration changes it; leave this alone |
FEATURE_CATALOGUE_CHECK |
Emergency recovery only — leave unset. At start-up each server checks that its list of operational controls agrees with the database. Default strict: a disagreement (a control the product reads has no database row, or a default, type or scope differs) stops the server and names the problem. Set to warn to start the server anyway while the catalogue is repaired, then remove it and restart — while it is set, Operational Controls may show a control whose default or scope disagrees with the product. Any other value is treated as strict. A control in the database that this build does not know is never a failure in either mode: the server starts and that control is hidden. See Operational Controls cutover |
THEME_CACHE_TTL_MS |
How long an organization's resolved theme is cached. Same 60-second default and same "why did my change take a moment" behavior as FEATURE_CACHE_TTL_MS — see Branding and Theming |
THEME_ASSET_MAX_MB |
Maximum upload size for a logo, favicon, or login hero image. Default 5 MB |
FEATURE_CATALOGUE_CHECK |
Emergency recovery only — leave unset. At start-up each server checks that its list of operational controls agrees with the database. Default strict: a disagreement (a control the product reads has no database row, or a default, type or scope differs) stops the server and names the problem. Set to warn to start the server anyway while the catalogue is repaired, then remove it and restart — while it is set, Operational Controls may show a control whose default or scope disagrees with the product. Any other value is treated as strict. A control in the database that this build does not know is never a failure in either mode: the server starts and that control is hidden. See Operational Controls cutover |
THEME_SVG_SANITIZE_ENABLED |
Whether uploaded SVG theme assets (logo/favicon/hero) are sanitized before storage. Leave enabled (default) — this closes a stored-XSS path; disabling it is an emergency rollback switch only, for if a legitimate logo is ever incorrectly rejected, not a setting to turn off routinely |
SCHEMA_GUARD |
Startup schema verification. Leave enabled — disabling it restores the failure mode it exists to prevent |
FVU_JAR_PATH / TDS_OUT_DIR |
TDS return validation tooling and output directory |
DOTENV_PATH |
Alternative configuration file location |
SENTRY_DSN |
Sentry project DSN for exception aggregation. Absent by default — error reporting to Sentry is a true no-op (no network calls) until this is set. See Logging & Observability |
OTEL_ENABLED |
Enables OpenTelemetry distributed tracing. Off everywhere today — non-production bootstrap only; production rollout is a separate, deliberate step (requires -r ./otel-bootstrap.js / PM2 node_args preloading, not just setting this flag, for HTTP/Express spans to instrument correctly). See Logging & Observability |
OTEL_EXPORTER_OTLP_ENDPOINT |
Where traces are sent when tracing is enabled. Absent falls back to a console exporter (for local verification only) |
Dev/POC tooling — mTLS bridge over ngrok¶
gst-backend/scripts/dev-mtls/ws-bridge-server.js and ws-bridge-client.js are a development-only pair of scripts that let the Complifly Agent's mTLS connection travel through a free ngrok HTTP tunnel (which cannot carry raw mTLS, since ngrok ends TLS at its own edge on the free tier) by carrying the TLS bytes inside a WebSocket instead. Not used in any deployed environment.
| Setting | Purpose |
|---|---|
BRIDGE_PORT |
Server side (ws-bridge-server.js). Port the bridge listens on. Default 8444 |
BRIDGE_TARGET |
Server side. The local mTLS gateway to forward decoded traffic to. Default 127.0.0.1:8443 |
BRIDGE_URL |
Client side (ws-bridge-client.js). The ngrok WebSocket address to connect to. No default — required |
BRIDGE_LISTEN |
Client side. Local port the client listens on, for the agent script to point at. Default 9443 |
Complete reference¶
The sections above group the settings you will actually reach for, with the reasoning behind each. This table is the exhaustive list — every variable the backend reads, generated from the source so it cannot fall behind a release.
Treat an unfamiliar variable here as a default you are accepting rather than a setting you are missing. Most of them should stay untouched.
Every non-GSP variable this build reads, with the default that applies when you do not set it. GSP endpoints and credentials are in GSP Configuration.
| Variable | Default | Read by |
|---|---|---|
AGENT_GATEWAY_SECRET |
Secret — no default | middleware/agentAuth.middleware.js, scripts/dev-mtls/local-mtls-gateway.js |
ANTHROPIC_API_KEY |
Secret — no default | config/compass.config.js, services/compass/agent/config.js |
APP_BASE_URL |
http://localhost:8080 |
routes/attentionNotifications.routes.js, routes/auth.routes.js +2 |
APP_NAME |
GST Backend API |
serverStart.js |
ATTENTION_NOTIFICATIONS_ENABLED |
none | services/attention/notifications/dispatcher.js |
ATTENTION_NOTIFICATIONS_INTERVAL_MS |
none | services/attention/notifications/dispatcher.js |
AUDIT_ARCHIVE_ENABLED |
false |
services/housekeeping/auditArchive.service.js |
AUDIT_ARCHIVE_INTERVAL_MS |
none | services/housekeeping/auditArchive.service.js |
AUTH_RATE_LIMIT_ENABLED |
none | config/authRateLimit.js, scripts/preflight_commercial_stack.js |
AUTH_RATE_LIMIT_MAX |
none | config/authRateLimit.js |
AZURE_API_APP_ID |
none | routes/erp-connection.routes.js |
AZURE_API_AUDIENCE |
empty string | middleware/erpAuth.middleware.js, routes/erp-connection.routes.js |
AZURE_CLIENT_ID |
none | services/social-auth.service.js |
AZURE_COMMUNICATION_CONNECTION_STRING |
none | routes/attentionNotifications.routes.js, services/attention/notifications/dispatcher.js +2 |
AZURE_EMAIL_SENDER_ADDRESS |
none | routes/attentionNotifications.routes.js, services/attention/notifications/dispatcher.js +2 |
AZURE_REQUIRED_ROLE |
Invoice.Post |
middleware/erpAuth.middleware.js |
AZURE_SEARCH_API_KEY |
Secret — no default | config/compass.config.js |
AZURE_SEARCH_ENDPOINT |
empty string | config/compass.config.js |
AZURE_SEARCH_INDEX |
complifly-knowledge |
config/compass.config.js |
AZURE_SEARCH_USE_VECTORS |
none | config/compass.config.js |
AZURE_TENANT_ID |
empty string | middleware/erpAuth.middleware.js, services/social-auth.service.js |
BRIDGE_LISTEN |
9443 |
scripts/dev-mtls/ws-bridge-client.js |
BRIDGE_PORT |
8444 |
scripts/dev-mtls/ws-bridge-server.js |
BRIDGE_TARGET |
127.0.0.1:8443 |
scripts/dev-mtls/ws-bridge-server.js |
BRIDGE_URL |
none | scripts/dev-mtls/ws-bridge-client.js |
CERT_DIR |
none | scripts/dev-mtls/machineA-agent-poc.js |
COMMERCIAL_CAPACITY_ALERTS_ENABLED |
none | services/commercial/capacityAlerts.service.js |
COMMERCIAL_CAPACITY_ALERTS_INTERVAL_MS |
none | services/commercial/capacityAlerts.service.js |
COMMERCIAL_PROMOTION_SWEEP_INTERVAL_MS |
none | services/commercial/promotionSweep.service.js |
COMPASS_MODEL |
none | config/compass.config.js |
COMPASS_RETRIEVAL_BACKEND |
local |
config/compass.config.js |
COMPLIANCE_HOOKS_ENABLED |
none | services/attention/config.js |
COMPLIANCE_SWEEP_ENABLED |
none | services/attention/config.js |
COMPLIANCE_SWEEP_INTERVAL_MS |
none | services/attention/config.js |
CORS_EXTRA_ORIGINS |
none | config/domains.js |
CORS_ORIGINS |
http://localhost:8080 |
serverStart.js |
DB_APP_NAME |
einvoice-service |
config/db.config.js |
DB_CONNECTION_TIMEOUT_MS |
15000 |
config/db.config.js |
DB_DATABASE |
empty string | config/db.config.js, scripts/audit_role_audience.js +2 |
DB_ENCRYPT |
false |
config/db.config.js |
DB_NAME |
(see .env) |
scripts/check_stgheaderid_integrity.js |
DB_PASSWORD |
Secret — no default | config/db.config.js |
DB_POOL_IDLE_TIMEOUT_MS |
30000 |
config/db.config.js |
DB_POOL_MAX |
20 |
config/db.config.js |
DB_POOL_MIN |
4 |
config/db.config.js |
DB_PORT |
1433 |
config/db.config.js |
DB_REQUEST_TIMEOUT_MS |
30000 |
config/db.config.js |
DB_SERVER |
empty string | config/db.config.js, scripts/audit_role_audience.js +3 |
DB_TRUST_SERVER_CERT |
true |
config/db.config.js |
DB_USER |
empty string | config/db.config.js |
DOMAIN_API |
https://api.complifly.in |
config/domains.js |
DOMAIN_APP |
https://app.complifly.in |
config/domains.js |
DOMAIN_DOCS |
https://docs.complifly.in |
config/domains.js |
DOMAIN_FILES |
https://files.complifly.online |
config/domains.js |
DOMAIN_HELP |
https://help.complifly.in |
config/domains.js |
DOMAIN_MARKETING |
https://www.complifly.info |
config/domains.js |
DOMAIN_STATUS |
https://status.complifly.tech |
config/domains.js |
DOMAIN_TUNNEL |
none | config/domains.js |
DOTENV_PATH |
none | config/db.config.js, config/mastersIndia.config.js +13 |
EF_PASS |
none | config/nic.config.js |
EF_USER |
none | config/nic.config.js |
EINV_PASS |
none | config/nic.config.js |
EINV_USER |
none | config/nic.config.js |
ERP_CONSENT_REDIRECT_URI |
none | routes/erp-connection.routes.js |
ERP_WRITEBACK_ALERTS_ENABLED |
false |
services/integration/alerter.service.js |
ERP_WRITEBACK_ALLOW_INSECURE_URL |
false |
services/integration/egressGuard.js |
ERP_WRITEBACK_BACKFILL_MAX_DOCS |
none | services/integration/backfill.service.js |
ERP_WRITEBACK_BATCH_SIZE |
none | services/integration/dispatcher.service.js |
ERP_WRITEBACK_CIRCUIT_COOLDOWN_CAP_SEC |
none | services/integration/circuit.service.js |
ERP_WRITEBACK_CIRCUIT_COOLDOWN_SEC |
none | services/integration/circuit.service.js |
ERP_WRITEBACK_CIRCUIT_THRESHOLD |
none | services/integration/circuit.service.js |
ERP_WRITEBACK_DEAD_RETENTION_DAYS |
none | services/integration/retention.service.js |
ERP_WRITEBACK_DISPATCH_INTERVAL_MS |
none | services/integration/dispatcher.service.js |
ERP_WRITEBACK_EMIT_ONLY |
false |
routes/writeback-subscription.routes.js, services/integration/dispatcherRunner.js +1 |
ERP_WRITEBACK_ENABLED |
false |
routes/writeback-subscription.routes.js, services/integration/dispatcherRunner.js +1 |
ERP_WRITEBACK_LEASE_SEC |
none | services/integration/dispatcher.service.js |
ERP_WRITEBACK_PURGE_BATCH |
none | services/integration/retention.service.js |
ERP_WRITEBACK_RAW_PAYLOAD_DAYS |
none | services/integration/retention.service.js |
ERP_WRITEBACK_RECONCILE_CAP |
none | services/integration/reconciler.service.js |
ERP_WRITEBACK_RECONCILE_ENABLED |
false |
services/integration/reconciler.service.js |
ERP_WRITEBACK_RECONCILE_INTERVAL_MS |
none | services/integration/reconciler.service.js |
ERP_WRITEBACK_RECONCILE_LOOKBACK_DAYS |
none | services/integration/reconciler.service.js |
ERP_WRITEBACK_REQUEUE_STAGGER_SEC |
none | services/integration/recovery.service.js |
ERP_WRITEBACK_RETENTION_DAYS |
none | services/integration/retention.service.js |
ERP_WRITEBACK_RETENTION_ENABLED |
false |
services/integration/retention.service.js |
ERP_WRITEBACK_RETENTION_INTERVAL_MS |
none | services/integration/retention.service.js |
ERP_WRITEBACK_SUPPORT_EMAIL |
empty string | services/integration/alerter.service.js |
ERP_WRITEBACK_WORKER_ID |
none | services/integration/dispatcher.service.js |
EWB_EXPIRY_ALERTS_ENABLED |
none | services/notifications/ewbExpiryAlert.service.js |
EWB_EXPIRY_HORIZON_HOURS |
none | services/notifications/ewbExpiryAlert.service.js |
EWB_GENERATION_GUARD |
ENFORCE |
services/ewb/ewbSubmit.service.js |
EWB_PERSIST_MAX_ATTEMPTS |
none | services/ewb/ewbSubmit.service.js |
EWB_PRINT_HORIZON_MONTHS |
2 |
services/ewb/printEwbDoc.service.js |
EWB_SUBMIT_BASE_DELAY_MS |
none | services/ewb/ewbSubmit.service.js |
EWB_SUBMIT_MAX_ATTEMPTS |
none | services/ewb/ewbSubmit.service.js |
FEATURE_CACHE_TTL_MS |
60000 |
services/effectiveFeature.service.js, services/feature.service.js |
FEATURE_CATALOGUE_CHECK |
strict |
services/featureDefinition.service.js |
FEATURE_DEFINITION_CACHE_TTL_MS |
300000 |
services/featureDefinition.service.js |
FILE_UPLOAD_DIR |
none | routes/theme.routes.js, routes/upload.routes.js |
FINDING_RETENTION_ENABLED |
false |
services/housekeeping/findingRetention.service.js |
FINDING_RETENTION_INTERVAL_MS |
none | services/housekeeping/findingRetention.service.js |
FVU_JAR_PATH |
none | services/tds/fvuService.js |
GOOGLE_CLIENT_ID |
none | services/social-auth.service.js |
GROWTH_METRICS_ENABLED |
false |
services/housekeeping/growthMetrics.service.js |
GROWTH_METRICS_INTERVAL_MS |
none | services/housekeeping/growthMetrics.service.js |
GROWTH_METRICS_PURGE_BATCH |
none | services/housekeeping/growthMetrics.service.js |
GROWTH_METRICS_RETENTION_DAYS |
none | services/housekeeping/growthMetrics.service.js |
GSP_CONFIG_CACHE_TTL_MS |
60000 |
services/GSP/gspResolver.service.js |
HEALTH_DEEP_TIMEOUT_MS |
none | routes/health.routes.js |
HOSTNAME |
unknown |
services/jwtKeys.js |
HOUSEKEEPING_RUN_ON_STARTUP |
none | services/housekeeping/jobScheduler.js |
INGEST_IN_FLIGHT_TIMEOUT_MS |
none | services/integration/ingestProtocol.service.js |
INSIGHTS_ENABLED |
none | services/insights/config.js |
IRN_SUBMIT_BASE_DELAY_MS |
none | services/IRN/irnSubmit.service.js |
IRN_SUBMIT_MAX_ATTEMPTS |
none | services/IRN/irnSubmit.service.js |
JOBS_USE_QUEUE |
false |
services/queue/index.js |
JWT_KEY_ID |
Secret — no default | services/jwtKeys.js |
JWT_PREVIOUS_KEY_ID |
Secret — no default | services/jwtKeys.js |
JWT_PREVIOUS_VALID_UNTIL |
empty string | services/jwtKeys.js |
JWT_SECRET |
Secret — no default | routes/auth.routes.js, scripts/test_consent_direct.js +3 |
JWT_SECRET_PREVIOUS |
Secret — no default | services/jwtKeys.js |
KEY_VAULT_URL |
none | services/secrets/index.js, services/secrets/provider.js |
LOGIN_FAILURE_ALERT_PER_USER_THRESHOLD |
none | services/observability/healthSweep.service.js |
LOGIN_FAILURE_ALERT_THRESHOLD |
none | services/observability/healthSweep.service.js |
LOG_LEVEL |
info |
config/logger.js |
LOG_PRETTY |
empty string | config/logger.js |
MASTER_CACHE_TTL_MS |
none | services/mastersCache.service.js |
MASTER_QUERY_CACHE_TTL_MS |
none | services/mastersCache.service.js |
MAX_FILE_SIZE_MB |
none | routes/upload.routes.js |
MTLS_CA_CERT |
none | scripts/dev-mtls/local-mtls-gateway.js |
MTLS_GATEWAY_CERT |
none | scripts/dev-mtls/local-mtls-gateway.js |
MTLS_GATEWAY_KEY |
Secret — no default | scripts/dev-mtls/local-mtls-gateway.js |
MTLS_GATEWAY_PORT |
8443 |
scripts/dev-mtls/local-mtls-gateway.js |
MTLS_UPSTREAM_URL |
http://127.0.0.1:5003 |
scripts/dev-mtls/local-mtls-gateway.js |
NOTIFICATION_RETENTION_ENABLED |
false |
services/housekeeping/notificationCleanup.service.js |
NOTIFICATION_RETENTION_INTERVAL_MS |
none | services/housekeeping/notificationCleanup.service.js |
ORG_SESSION_POLICY_CACHE_TTL_MS |
60000 |
services/orgSessionPolicy.service.js |
OTEL_ENABLED |
0 |
otel-bootstrap.js |
OTEL_EXPORTER_OTLP_ENDPOINT |
none | otel-bootstrap.js |
OTP_EXPIRY_MINUTES |
5 |
services/otp.service.js |
OTP_MAX_ATTEMPTS |
5 |
services/otp.service.js |
OTP_RESEND_COOLDOWN_SECONDS |
60 |
services/otp.service.js |
PAN_ENCRYPTION_KEY |
Secret — no default | services/privacy/privacyService.js |
PDF_CACHE_RETENTION_ENABLED |
false |
services/housekeeping/pdfCacheRetention.service.js |
PDF_CACHE_RETENTION_INTERVAL_MS |
none | services/housekeeping/pdfCacheRetention.service.js |
PLATFORM_ALERT_MIN_SAMPLE_EWB |
none | services/observability/healthSweep.service.js |
PLATFORM_ALERT_MIN_SAMPLE_IRN |
none | services/observability/healthSweep.service.js |
PLATFORM_ALERT_MIN_SAMPLE_UPLOAD |
none | services/observability/healthSweep.service.js |
PLATFORM_ALERT_RECIPIENTS |
empty string | services/observability/healthSweep.service.js |
PLATFORM_EVENT_LOG_PURGE_BATCH |
none | services/observability/eventLogRetention.service.js |
PLATFORM_EVENT_LOG_RETENTION_DAYS |
none | services/observability/eventLogRetention.service.js |
PLATFORM_EVENT_LOG_RETENTION_ENABLED |
false |
services/observability/eventLogRetention.service.js |
PLATFORM_EVENT_LOG_RETENTION_INTERVAL_MS |
none | services/observability/eventLogRetention.service.js |
PLATFORM_HEALTH_SWEEP_ENABLED |
none | services/observability/healthSweep.service.js |
POLICY_GATE_EVENTS_ENABLED |
none | services/policy/gateHealth.js |
POLICY_GATE_EVENT_THROTTLE_MS |
none | services/policy/gateHealth.js |
PORT |
5003 |
scripts/test_consent_direct.js, scripts/test_consent_gate.js +2 |
QUARANTINE_ARCHIVE_ENABLED |
false |
services/housekeeping/quarantineArchive.service.js |
QUARANTINE_ARCHIVE_INTERVAL_MS |
none | services/housekeeping/quarantineArchive.service.js |
QUEUE_RETENTION_ENABLED |
false |
services/housekeeping/queueRetention.service.js |
QUEUE_RETENTION_INTERVAL_MS |
none | services/housekeeping/queueRetention.service.js |
REDIS_CACHE_DB |
0 |
services/cache.js |
REDIS_CACHE_OP_TIMEOUT_MS |
250 |
services/cache.js |
REDIS_HOST |
127.0.0.1 |
config/rateLimitRedis.js, services/cache.js +1 |
REDIS_PASSWORD |
Secret — no default | config/rateLimitRedis.js, services/cache.js +1 |
REDIS_PORT |
6379 |
config/rateLimitRedis.js, services/cache.js +1 |
REDIS_RATELIMIT_DB |
2 |
config/rateLimitRedis.js |
SCHEMA_GUARD |
empty string | config/schemaGuard.js |
SECURITY_REPORT_ENABLED |
none | serverStart.js |
SENTRY_DSN |
none | config/sentry.js |
SENTRY_TRACES_SAMPLE_RATE |
none | config/sentry.js |
SESSION_MAX_CONCURRENT |
1 |
services/orgSessionPolicy.service.js, services/session.service.js |
SESSION_REAPER_ENABLED |
false |
services/housekeeping/sessionCleanup.service.js |
SESSION_REAPER_INTERVAL_MS |
none | services/housekeeping/sessionCleanup.service.js |
SESSION_TOUCH_THROTTLE_MS |
60000 |
services/session.service.js |
SESSION_TTL_HOURS |
8 |
services/session.service.js |
SHUTDOWN_TIMEOUT_MS |
none | serverStart.js |
TDS_CSI_RETENTION_DAYS |
none | services/housekeeping/fileLifecycle.service.js |
TDS_CSI_RETENTION_ENABLED |
false |
services/housekeeping/fileLifecycle.service.js |
TDS_CSI_RETENTION_INTERVAL_MS |
none | services/housekeeping/fileLifecycle.service.js |
TDS_OUT_DIR |
none | services/tds/fvuService.js |
THEME_ASSET_MAX_MB |
none | routes/theme.routes.js |
THEME_CACHE_TTL_MS |
60000 |
services/theme.service.js |
THEME_SVG_SANITIZE_ENABLED |
empty string | routes/theme.routes.js |
USER |
operator |
scripts/opcontrols_phase3.js |
USERNAME |
none | scripts/opcontrols_phase3.js |
USER_SESSION_POLICY_CACHE_TTL_MS |
60000 |
services/userSessionPolicy.service.js |
VERIFY_CACHE_ENABLED |
empty string | services/verificationCache.service.js |
VERIFY_CACHE_TTL_GSTIN_DAYS |
7 |
services/verificationCache.service.js |
VERIFY_CACHE_TTL_PAN_DAYS |
90 |
services/verificationCache.service.js |
VERIFY_CACHE_TTL_UDYAM_DAYS |
90 |
services/verificationCache.service.js |
WEBSITE_API_KEY_HASH |
Secret — no default | routes/privacy.routes.js, scripts/setup_website_dpdp.js +1 |
WEBSITE_NOTICE_VERSION_ID |
none | routes/privacy.routes.js, scripts/setup_website_dpdp.js +1 |
WEBSITE_PLAINTEXT_KEY |
Secret — no default | scripts/test_website_dpdp.js |
WEBSITE_TENANT_ID |
COMPLIFLY_WEBSITE |
routes/privacy.routes.js, scripts/test_website_dpdp.js |
WORKER_CONCURRENCY |
4 |
worker.js |
196 variables. Generated from the process.env reads in the backend — do not edit by hand; run node tools/docs/gen-env-reference.js --write.
Validation¶
After editing the environment file:
| Check | Method | Pass condition |
|---|---|---|
| File is valid | Restart the API and read the startup log | Starts cleanly |
| Restart actually happened | Compare the process start time | Recent. Editing without restarting is the most common "the change did nothing" cause |
| Worker restarted too | Check the worker | Restarted, if the setting affects background work |
| Database settings correct | Application starts and screens load | Connected |
| Port matches the proxy | Request through the proxy | HTTP 200, not 502 |
| Provider settings correct | Run the connection test | Success |
| Mail works | Trigger a password reset to a real mailbox | Delivered |
| Queue works, if enabled | Submit an upload | Job completes rather than queuing indefinitely |
| Permissions restricted | Inspect the file's permissions | Readable only by the service account and administrators |
| Not in source control | Check the repository | Absent |
| Sandbox and production separated | Inspect provider URLs per environment | Non-production points only at sandbox |
Troubleshooting¶
| Symptom | Cause | Action |
|---|---|---|
| Change had no effect | No restart, or the wrong process restarted | Restart the API, and the worker if relevant |
| API will not start after an edit | Malformed file, or a required value missing | Read the startup log; it usually names the setting |
| HTTP 502 through the proxy | Port mismatch between service and proxy | Align them |
| Users cannot sign in after a change | The token signing secret changed | Every existing session is invalidated. Expected, but only if intended |
| Encrypted identity values unreadable | The encryption key changed or was lost | Not recoverable from a database backup. Restore the original key |
| No emails at all | Mail settings wrong, or the mail path blocked | Test from the application host. This locks users out silently |
| Uploads never process | Queue-backed jobs enabled with the queue unreachable | Check the queue. No error appears in the interface |
| Master data or a feature flag change takes a moment to appear everywhere | Normal — the relevant TTL hasn't elapsed on every app server yet | Expected. Shorten the TTL if the delay genuinely matters for this deployment |
| Rate limits seem looser than documented across a multi-server deployment | Redis unreachable — the limiter fails open rather than blocking the request | Restore the Redis connection. This is deliberate: a caching outage must never turn into an availability outage |
| Provider calls fail after working | Credentials rotated, endpoint changed, or egress address changed | Run the connection test; check the egress address the provider sees |
| Write-back stops after a burst of failures | The circuit breaker opened | Expected protective behaviour. Fix the endpoint; delivery resumes after cooldown |
| Dead-lettered events disappeared before investigation | Dead-letter retention shorter than reaction time | Raise it. Evidence purged before anyone looks is evidence lost |
| Verification data is stale | Cache window longer than the business needs | Shorten the relevant window, accepting the extra provider cost |
| Environments diverge | Configuration drift | Compare files between environments regularly |
Related Articles¶
- GSP Configuration — provider assignment and testing
- Email and Notifications — mail in depth
- Feature Flags — the per-GSTIN layer
- Security Hardening — protecting this file
- Write-back API — what the write-back settings govern