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 |
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 |
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 |
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 |
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¶
| Setting | Purpose | Failure if wrong |
|---|---|---|
SMTP_HOST / SMTP_PORT |
Mail server | No notifications, no password reset, no one-time codes. Users are locked out with no visible cause |
SMTP_SECURE |
Whether the connection is implicitly encrypted | Handshake failure |
SMTP_USER / SMTP_PASS |
Mail credentials | Authentication failure at send time, not at startup |
SMTP_FROM |
Sender address | Messages rejected or filtered as spam if it fails sender policy checks |
AZURE_COMMUNICATION_CONNECTION_STRING |
Vendor mail service, as an alternative to SMTP | — |
AZURE_EMAIL_SENDER_ADDRESS |
Sender address for the vendor mail service | — |
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".
Background jobs and queue¶
| Setting | Purpose | Note |
|---|---|---|
JOBS_USE_QUEUE |
Whether background work is queue-backed | When enabled, Redis becomes a hard dependency |
REDIS_HOST / REDIS_PORT / REDIS_PASSWORD |
Queue connection | Silent failure: jobs enqueue and are never consumed. The interface looks entirely healthy |
WORKER_CONCURRENCY |
Parallel jobs per worker | 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 |
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 per-GSTIN feature flags are cached. Explains why a flag change takes a moment to appear |
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 |
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 |
|---|---|---|
APP_BASE_URL |
http://localhost:8080 |
routes/auth.routes.js, services/azure-email.service.js |
APP_NAME |
GST Backend API |
server.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 | services/azure-email.service.js, services/notifications/ewbExpiryAlert.service.js |
AZURE_EMAIL_SENDER_ADDRESS |
none | services/azure-email.service.js, services/notifications/ewbExpiryAlert.service.js |
AZURE_REQUIRED_ROLE |
Invoice.Post |
middleware/erpAuth.middleware.js |
AZURE_TENANT_ID |
empty string | middleware/erpAuth.middleware.js, services/social-auth.service.js |
CORS_ORIGINS |
http://localhost:8080 |
server.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 |
DB_ENCRYPT |
false |
config/db.config.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 |
DB_TRUST_SERVER_CERT |
true |
config/db.config.js |
DB_USER |
empty string | config/db.config.js |
DOTENV_PATH |
none | config/db.config.js, config/mastersIndia.config.js +9 |
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_PRINT_HORIZON_MONTHS |
2 |
services/ewb/printEwbDoc.service.js |
FEATURE_CACHE_TTL_MS |
60000 |
services/feature.service.js |
FILE_UPLOAD_DIR |
none | routes/upload.routes.js |
FVU_JAR_PATH |
none | services/tds/fvuService.js |
GOOGLE_CLIENT_ID |
none | services/social-auth.service.js |
GSP_CONFIG_CACHE_TTL_MS |
60000 |
services/GSP/gspResolver.service.js |
INGEST_IN_FLIGHT_TIMEOUT_MS |
none | services/integration/ingestProtocol.service.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_SECRET |
Secret — no default | middleware/auth.middleware.js, routes/auth.routes.js +3 |
LOG_LEVEL |
info |
config/logger.js |
LOG_PRETTY |
empty string | config/logger.js |
MAX_FILE_SIZE_MB |
none | routes/upload.routes.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 |
PORT |
5003 |
scripts/test_consent_direct.js, scripts/test_consent_gate.js +2 |
REDIS_HOST |
127.0.0.1 |
services/queue/index.js |
REDIS_PASSWORD |
Secret — no default | services/queue/index.js |
REDIS_PORT |
6379 |
services/queue/index.js |
SCHEMA_GUARD |
empty string | config/schemaGuard.js |
SMTP_FROM |
none | services/email.service.js |
SMTP_HOST |
none | services/email.service.js |
SMTP_PASS |
none | services/email.service.js |
SMTP_PORT |
587 |
services/email.service.js |
SMTP_SECURE |
none | services/email.service.js |
SMTP_USER |
none | services/email.service.js |
TDS_OUT_DIR |
none | services/tds/fvuService.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 |
94 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 |
| 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